Pourquoi `cp` a-t-il été conçu pour remplacer silencieusement les fichiers existants? [fermé]

30

J'ai testé cpavec les commandes suivantes:

$ ls
first.html   second.html  third.html

$ cat first.html
first

$ cat second.html
second

$ cat third.html
third

Ensuite, je copie first.htmlà second.html:

$ cp first.html second.html

$ cat second.html
first

Le fichier second.htmlest écrasé en silence sans aucune erreur. Cependant, si je le fais dans une interface graphique de bureau en faisant glisser et en déposant un fichier avec le même nom, il sera suffixé comme first1.htmlautomatiquement. Cela évite d'écraser accidentellement un fichier existant.

Pourquoi ne cpsuit-il pas ce modèle au lieu d'écraser les fichiers en silence?

Algèbre
la source
10
J'imagine que seuls les concepteurs de coreutils peuvent vraiment répondre à la question, mais c'est juste la façon dont cela fonctionne pour l'instant. Habituellement, les applications sont construites en supposant que l'utilisateur signifie vraiment ce qu'il fait et pour minimiser les invites supplémentaires. Si vous souhaitez modifier le comportement, alias 'cp' en 'cp ​​-i' ou 'cp -n'.
kevlinux
8
@kevlinux Les développeurs coreutils implémentent simplement la norme POSIX.
Kusalananda
17
Parce qu'à l'époque où il a été conçu, les gens voulaient être aussi concis que possible avec ce qu'ils faisaient (donc cp pas copier) et savaient ce qu'ils faisaient et lorsqu'ils faisaient des erreurs, ils n'essayaient pas de blâmer les outils. C'était un type de personnes totalement différent à l'époque qui faisait des ordinateurs. C'est comme demander pourquoi un scalpel pour un chirurgien cardiaque peut aussi se couper les mains.
PlasmaHH
4
Unix a été conçu par et pour des experts en informatique, en supposant que l'utilisateur savait ce qu'il faisait. L'OS ferait exactement ce que l'utilisateur lui a dit si possible - sans tenir la main de l'utilisateur et sans demander de confirmations sans fin. Si une opération écrase quelque chose, il est supposé que c'est ce que l'utilisateur souhaite. Rappelez-vous également que c'était au début des années 1970 - pré-MS DOS, Windows et ordinateurs personnels - guider et tenir la main de l'utilisateur à chaque étape du processus, n'était pas encore courant. De plus, avec le télétype usiné comme terminaux, demander des confirmations serait toujours trop lourd.
Baard Kopperud
10
Ne vous alias cppas cp -iou similaire parce que vous vous habituerez à avoir un filet de sécurité, rendant les systèmes là où ils ne sont pas disponibles (la plupart d'entre eux) beaucoup plus risqués. Mieux vaut vous apprendre régulièrement, cp -ietc. si c'est ce que vous préférez.
Reid

Réponses:

52

Le comportement d'écrasement par défaut de cpest spécifié dans POSIX.

  1. Si le fichier source est de type fichier normal, les étapes suivantes doivent être suivies:

    3.a. Le comportement n'est pas spécifié si dest_file existe et a été écrit lors d'une étape précédente. Sinon, si dest_file existe, les étapes suivantes doivent être suivies:

    3.ai Si l'option -i est en vigueur, l'utilitaire cp doit écrire une invite à l'erreur standard et lire une ligne à partir de l'entrée standard. Si la réponse n'est pas affirmative, cp ne fera plus rien avec source_file et passera aux fichiers restants.

    3.a.ii. Un descripteur de fichier pour dest_file doit être obtenu en effectuant des actions équivalentes à la fonction open () définie dans le volume System Interfaces de POSIX.1-2017 appelée en utilisant dest_file comme argument de chemin, et le OU inclus au niveau du bit de O_WRONLY et O_TRUNC comme argument oflag.

    3.a.iii. Si la tentative d'obtention d'un descripteur de fichier échoue et que l'option -f est en vigueur, cp doit tenter de supprimer le fichier en effectuant des actions équivalentes à la fonction unlink () définie dans le volume System Interfaces de POSIX.1-2017 appelé à l'aide de dest_file comme argument de chemin. Si cette tentative réussit, cp continuera avec l'étape 3b.

Lorsque la spécification POSIX a été écrite, il existait déjà un grand nombre de scripts, avec une hypothèse intégrée pour le comportement d'écrasement par défaut. Beaucoup de ces scripts ont été conçus pour s'exécuter sans présence directe de l'utilisateur, par exemple en tant que tâches cron ou autres tâches d'arrière-plan. Changer le comportement les aurait brisés. Les examiner et les modifier tous pour ajouter une option pour forcer l'écrasement partout où cela était nécessaire était probablement considéré comme une tâche énorme avec des avantages minimes.

De plus, la ligne de commande Unix a toujours été conçue pour permettre à un utilisateur expérimenté de travailler efficacement, même au détriment d'une courbe d'apprentissage difficile pour un débutant. Lorsque l'utilisateur entre une commande, l'ordinateur doit s'attendre à ce que l'utilisateur le veuille vraiment, sans aucune hésitation; il est de la responsabilité de l'utilisateur de faire attention aux commandes potentiellement destructrices.

Lorsque l'Unix d'origine a été développé, les systèmes avaient alors si peu de mémoire et de stockage de masse par rapport aux ordinateurs modernes que l'écrasement des avertissements et des invites était probablement considéré comme un luxe inutile et inutile.

Lorsque la norme POSIX a été rédigée, le précédent était fermement établi et les rédacteurs de la norme étaient bien conscients des vertus de ne pas rompre la rétrocompatibilité .

En outre, comme d'autres l'ont décrit, tout utilisateur peut ajouter / activer ces fonctionnalités pour lui-même, en utilisant des alias de shell ou même en créant une cpcommande de remplacement et en les modifiant $PATHpour trouver le remplacement avant la commande système standard, et obtenir le filet de sécurité de cette façon si voulu.

Mais si vous le faites, vous constaterez que vous créez un danger pour vous-même. Si la cpcommande se comporte d'une manière lorsqu'elle est utilisée de manière interactive et d'une autre façon lorsqu'elle est appelée à partir d'un script, vous ne vous souvenez peut-être pas que la différence existe. Sur un autre système, vous pourriez finir par être négligent parce que vous êtes habitué aux avertissements et aux invites de votre propre système.

Si le comportement des scripts correspond toujours à la norme POSIX, vous vous habituerez probablement aux invites en utilisation interactive, puis écrivez un script qui effectue une copie de masse - et constatez ensuite que vous avez écrasé quelque chose par inadvertance.

Si vous appliquez également l'invite dans les scripts, que fera la commande lorsqu'elle sera exécutée dans un contexte sans utilisateur, par exemple des processus d'arrière-plan ou des tâches cron? Le script sera-t-il bloqué, abandonné ou écrasé?

Suspendre ou abandonner signifie qu'une tâche qui était censée être effectuée automatiquement ne sera pas effectuée. Le non-écrasement peut parfois également causer un problème en lui-même: par exemple, il peut entraîner le traitement deux fois des anciennes données par un autre système au lieu d'être remplacées par des données à jour.

Une grande partie de la puissance de la ligne de commande vient du fait qu'une fois que vous savez comment faire quelque chose sur la ligne de commande, vous saurez également implicitement comment le faire automatiquement par script . Mais cela n'est vrai que si les commandes que vous utilisez de manière interactive fonctionnent exactement de la même manière lorsqu'elles sont appelées dans un contexte de script. Toute différence significative de comportement entre une utilisation interactive et une utilisation scriptée créera une sorte de dissonance cognitive qui dérange un utilisateur expérimenté.

telcoM
la source
54
"Pourquoi ça marche comme ça?" "Parce que la norme le dit." "Pourquoi la norme le dit-elle?" "Parce que ça a déjà fonctionné, j'ai aimé."
Baptiste Candellier
16
Le dernier paragraphe est la vraie raison. Les boîtes de dialogue de confirmation et les invites " Voulez-vous vraiment faire cela? " Sont pour les mauviettes :-)
TripeHound
@BaptisteCandellier - D'accord. C'est comme si la raison ultime était là-bas, mais incroyablement juste hors de portée de cette réponse.
TED
2
Ce dernier paragraphe est pourquoi rm -rfest-il si efficace, même si vous ne vouliez pas l'exécuter dans votre répertoire personnel ...
Max Vernon
2
@TED drôle comment personne ne parle jamais comment le unlink (2) syscall aussi « ne » demander « Mère, puis - je? » Confirmation chaque fois que ces discussions sempiternels à nouveau la tête arrière de délicates. :)
tchrist
20

cpvient du début d'Unix. Il était là bien avant que la norme Posix ne soit écrite. En effet: Posix vient d'officialiser le comportement existant de cpà cet égard.

On parle d'Epoch (01/01/1970), quand les hommes étaient de vrais hommes, les femmes étaient de vraies femmes et de petites créatures à fourrure ... (je m'égare). À cette époque, l'ajout de code supplémentaire a agrandi le programme. C'était un problème alors, car le premier ordinateur qui exécutait Unix était un PDP-7 (extensible à 144 Ko de RAM!). Les choses étaient donc petites, efficaces, sans dispositifs de sécurité.

Donc, à cette époque, vous deviez savoir ce que vous faisiez, car l'ordinateur n'avait tout simplement pas le pouvoir de vous empêcher de faire tout ce que vous regrettiez plus tard.

(Il y a un joli dessin animé de Zevar; recherchez "zevar cerveaux assiste par ordinateur" pour retrouver l'évolution de l'ordinateur. Ou essayez http://perinet.blogspirit.com/archive/2012/02/12/zevar-et- cointe.html tant qu'il existe)

Pour ceux qui sont vraiment intéressés (j'ai vu des spéculations dans les commentaires): L'original cpsur le premier Unix était d'environ deux pages de code assembleur (C est venu plus tard). La partie pertinente était:

sys open; name1: 0; 0   " Open the input file
spa
  jmp error         " File open error
lac o17         " Why load 15 (017) into AC?
sys creat; name2: 0     " Create the output file
spa
  jmp error         " File create error

(Donc, dur sys creat)

Et, pendant que nous y sommes: la version 2 d'Unix est utilisée (code sniplet)

mode = buf[2] & 037;
if((fnew = creat(argv[2],mode)) < 0){
    stat(argv[2], buf);

ce qui est également difficile creatsans tests ni garanties. Notez que le code C pour V2 Unix cpest inférieur à 55 lignes!

Ljm Dullaart
la source
5
Presque correct, sauf que c'est " petit poilu " (créatures d'Alpha Centauri) pas " petit poilu "!
TripeHound
1
@TED: Il est tout à fait possible que les premières versions de la destination cpviennent d' éditer et de réaliser une boucle / ; Bien sûr, avec le moderne, il y a tellement de boutons qu'il doit essentiellement essayer de parvenir à destination à l'avance, et pourrait facilement vérifier l'existence en premier (et le fait avec / ), mais si les attentes ont été établies à partir d' outils originaux et dépouillés , changer ce comportement briserait inutilement les scripts existants. Ce n'est pas comme des coquilles modernes avec, après tout, ne peut pas simplement faire la valeur par défaut pour une utilisation interactive. openO_CREAT | O_TRUNCreadwritecpstatcp -icp -ncpaliascp -i
ShadowRanger
@ShadowRanger - Hmmm. Vous avez tout à fait raison de dire que je n'ai vraiment aucune idée si c'était facile ou difficile à faire. Commentaire supprimé.
TED
1
@ShadowRanger Oui, mais cela ne fait que pousser la dure leçon sur la route jusqu'à ce qu'elle soit sur un système de production ...
chrylis -on strike-
1
@sourcejedi: Fun! Cela ne change pas ma théorie de base (qu'il était plus facile de simplement ouvrir sans condition avec troncature, et creatse trouve être équivalent à open+ O_CREAT | O_TRUNC), mais le manque d' O_EXCLexpliquer pourquoi il n'aurait pas été aussi facile de gérer les fichiers existants; essayer de le faire serait intrinsèquement racé (vous devriez essentiellement vérifier open/ statvérifier l'existence, puis l'utiliser creat, mais sur les grands systèmes partagés, il est toujours possible au moment où vous y êtes arrivé creat, quelqu'un d'autre a créé le fichier et maintenant vous avez soufflé de toute façon loin). Peut aussi bien écraser sans condition.
ShadowRanger
19

Parce que ces commandes sont également destinées à être utilisées dans des scripts, s'exécutant éventuellement sans aucune supervision humaine, et aussi parce qu'il y a de nombreux cas où vous souhaitez en effet écraser la cible (la philosophie des shells Linux est que l'homme sait quoi elle fait)

Il y a encore quelques garanties:

  • GNU cpa un -n| --no-clobberoption
  • si vous copiez plusieurs fichiers dans un seul cp, vous vous plaindrez que le dernier n'est pas un répertoire.
xénoïde
la source
Cela s'applique uniquement à une implémentation spécifique au fournisseur et la question ne concernait pas cette implémentation spécifique au fournisseur.
schily
10

Est-ce "faire une chose à la fois"?

Ce commentaire ressemble à une question sur un principe général de conception. Souvent, les questions à ce sujet sont très subjectives et nous ne sommes pas en mesure d'écrire une réponse appropriée. Soyez averti que nous pouvons fermer les questions dans ce cas.

Parfois, nous avons une explication pour le choix de conception d'origine, car les développeurs ont écrit à leur sujet. Mais je n'ai pas une si belle réponse à cette question.

Pourquoi cpest conçu de cette façon?

Le problème est qu'Unix a plus de 40 ans.

Si vous créez un nouveau système maintenant, vous pouvez faire des choix de conception différents. Mais changer Unix briserait les scripts existants, comme mentionné dans d'autres réponses.

Pourquoi a- t-il étécp conçu pour remplacer silencieusement les fichiers existants?

La réponse courte est "je ne sais pas" :-).

Comprenez que ce cpn'est qu'un problème. Je pense qu'aucun des programmes de commande d'origine protégé contre l'écrasement ou la suppression de fichiers. Le shell a un problème similaire lors de la redirection de la sortie:

$ cat first.html > second.html

Cette commande écrase également silencieusement second.html.

Je suis curieux de savoir comment tous ces programmes pourraient être repensés. Cela peut nécessiter une complexité supplémentaire.

Je pense que cela fait partie de l'explication: les premiers Unix mettaient l'accent sur les implémentations simples . Pour une explication plus détaillée de cela, voir "pire c'est mieux", lié à la fin de cette réponse.

Vous pouvez changer > second.htmlpour qu'il s'arrête avec une erreur, s'il second.htmlexiste déjà. Cependant , comme nous l' avons mentionné, parfois l'utilisateur ne veut remplacer un fichier existant. Par exemple, elle peut créer une commande complexe, en essayant plusieurs fois jusqu'à ce qu'elle fasse ce qu'elle veut.

L'utilisateur peut s'exécuter en rm second.htmlpremier s'il en a besoin. Cela pourrait être un bon compromis! Il présente certains inconvénients possibles.

  1. L'utilisateur doit taper le nom de fichier deux fois.
  2. Les gens ont aussi beaucoup de mal à utiliser rm. Je voudrais donc rendre la rmsécurité aussi. Mais comment? Si nous faisons rmafficher chaque nom de fichier et demandons à l'utilisateur de confirmer, il doit maintenant écrire trois lignes de commandes au lieu d'une. De plus, si elle doit le faire trop souvent, elle prendra une habitude et tapera «y» pour confirmer sans réfléchir. Cela pourrait donc être très ennuyeux, et cela pourrait tout de même être dangereux.

Sur un système moderne, je recommande d' installer la trashcommande et de l'utiliser plutôt rmque possible. L'introduction du stockage Trash était une excellente idée, par exemple pour un PC graphique à utilisateur unique .

Je pense qu'il est également important de comprendre les limites du matériel Unix d'origine - RAM et espace disque limités, sortie affichée sur les imprimantes lentes ainsi que le système et le logiciel de développement.

Notez que Unix d'origine n'avait pas de tabulation , pour remplir rapidement un nom de fichier pour une rmcommande. (De plus, le shell Bourne d'origine n'a pas d'historique de commandes, par exemple lorsque vous utilisez la touche flèche vers le haut bash).

Avec la sortie de l' imprimante, vous devez utiliser éditeur en ligne, ed. C'est plus difficile à apprendre qu'un éditeur de texte visuel. Vous devez imprimer certaines lignes actuelles, décider comment vous souhaitez les modifier et saisir une commande d'édition.

Utiliser, > second.htmlc'est un peu comme utiliser une commande dans un éditeur de ligne. Son effet dépend de l'état actuel. (S'il second.htmlexiste déjà, son contenu sera supprimé). Si l'utilisateur n'est pas sûr de l'état actuel, il doit s'exécuter lsou d' ls second.htmlabord.

"Mise en œuvre simple" comme principe de conception

Il existe une interprétation populaire de la conception Unix, qui commence:

La conception doit être simple, à la fois dans la mise en œuvre et l'interface. Il est plus important que l'implémentation soit simple que l'interface. La simplicité est la considération la plus importante dans une conception.

...

Gabriel a fait valoir que "Worse is better" a produit des logiciels plus performants que l'approche MIT: tant que le programme initial est fondamentalement bon, il faudra beaucoup moins de temps et d'efforts pour le mettre en œuvre initialement et il sera plus facile de s'adapter aux nouvelles situations. Le portage de logiciels sur de nouvelles machines, par exemple, devient beaucoup plus facile de cette façon. Ainsi, son utilisation se répandra rapidement, bien avant qu'un [meilleur] programme ait une chance d'être développé et déployé (avantage du premier arrivant).

https://en.wikipedia.org/wiki/Worse_is_better

sourcejedi
la source
Pourquoi écraser la cible avec cpun "problème"? Le faire demander interactivement la permission, ou échouer, peut être un gros problème comme ça.
Kusalananda
Wow merci. compléter la ligne directrice: 1) Écrivez des programmes qui font une chose et le font bien. 2) Faites confiance au programmeur.
Algèbre
2
La perte de données @Kusalananda est un problème. Personnellement, je souhaite réduire le risque de perdre des données. Il existe différentes approches pour cela. Dire que c'est un problème ne signifie pas que les alternatives n'ont pas aussi de problèmes.
sourcejedi
1
@riderdragon Les programmes écrits en langage C peuvent souvent échouer de manière très surprenante, car C fait confiance au programmeur. Mais les programmeurs ne sont tout simplement pas fiables. Nous devons écrire des outils très avancés, comme valgrind , qui sont nécessaires pour essayer de trouver les erreurs commises par les programmeurs. Je pense qu'il est important d'avoir des langages de programmation comme Rust ou Python ou C # qui essaient d'appliquer la "sécurité mémoire" sans faire confiance au programmeur. (Le langage C a été créé par l'un des auteurs d'UNIX, afin d'écrire UNIX dans un langage portable).
sourcejedi
1
Encore mieux, cat first.html second.html > first.htmlcela entraînera l' first.htmlécrasement du contenu de second.htmlseulement. Le contenu original est perdu pour toujours.
doneal24
9

La conception de "cp" remonte à la conception originale d'Unix. En fait, il y avait une philosophie cohérente derrière la conception Unix, qui a été légèrement moins que ce qu'on appelle à moitié en plaisantant Worse-is-Better * .

L'idée de base est que garder le code simple est en fait une considération de conception plus importante qu'avoir une interface parfaite ou "faire la bonne chose".

  • Simplicité - la conception doit être simple, à la fois dans la mise en œuvre et l'interface. Il est plus important que l'implémentation soit simple que l'interface . La simplicité est la considération la plus importante dans une conception.

  • Exactitude - la conception doit être correcte dans tous les aspects observables. Il vaut mieux être simple que correct.

  • Cohérence - la conception ne doit pas être trop incohérente. La cohérence peut être sacrifiée pour la simplicité dans certains cas, mais il est préférable de supprimer les parties de la conception qui traitent de circonstances moins courantes que d'introduire une complexité de mise en œuvre ou une incohérence.

  • Complétude - la conception doit couvrir autant de situations importantes que possible. Tous les cas raisonnablement attendus doivent être couverts. L'exhaustivité peut être sacrifiée au profit de toute autre qualité. En fait, l'exhaustivité doit être sacrifiée chaque fois que la simplicité de mise en œuvre est compromise. La cohérence peut être sacrifiée pour atteindre l'exhaustivité si la simplicité est conservée; la cohérence de l'interface est particulièrement inutile.

(c'est moi qui souligne )

En se souvenant que c'était en 1970, le cas d'utilisation de "Je veux copier ce fichier uniquement s'il n'existe pas déjà" aurait été un cas d'utilisation assez rare pour quelqu'un qui effectuait une copie. Si c'est ce que vous vouliez, vous seriez tout à fait capable de vérifier avant la copie, et cela peut même être scripté.

Quant à savoir pourquoi un OS avec cette approche de conception s'est avéré être celui qui a gagné sur tous les autres OS en cours de construction à l'époque, l'auteur de l'essai avait également une théorie pour cela.

Un autre avantage de la philosophie du pire est le meilleur est que le programmeur est conditionné à sacrifier la sécurité, la commodité et les tracas pour obtenir de bonnes performances et une utilisation modeste des ressources. Les programmes écrits en utilisant l'approche du New Jersey fonctionneront bien à la fois sur les petites machines et les grandes, et le code sera portable car il est écrit sur un virus.

Il est important de se rappeler que le virus initial doit être fondamentalement bon. Dans l'affirmative, la propagation virale est assurée tant qu'elle est portable. Une fois que le virus s'est propagé, il y aura une pression pour l'améliorer, peut-être en augmentant sa fonctionnalité plus près de 90%, mais les utilisateurs ont déjà été conditionnés pour accepter pire que la bonne chose. Par conséquent, le logiciel le pire est le meilleur sera d'abord accepté, le second conditionnera ses utilisateurs à en attendre moins, et le troisième sera amélioré à un point qui est presque la bonne chose.

* - ou ce que l'auteur, mais personne d'autre, n'a appelé "l'approche du New Jersey" .

TED
la source
1
Ceci est la bonne réponse.
tchrist
+1, mais je pense qu'il serait utile d'avoir un exemple concret. Lorsque vous installez une nouvelle version d'un programme que vous avez édité et recompilé (et peut-être testé :-), vous voulez délibérément remplacer l'ancienne version du programme. (Et vous voulez probablement un comportement similaire de votre compilateur. Si tôt que UNIX a creat()vs open(). open()Impossible de créer un fichier si elle n'existait pas. Il ne faut 0/1/2 en lecture / écriture / les deux. Il ne prend pas encore O_CREAT, et il n'y en a pas O_EXCL).
sourcejedi
@sourcejedi - Désolé, mais en tant que développeur de logiciels moi-même, je ne peux honnêtement pas penser à un autre scénario que celui où je ferais une copie. :-)
TED
@TED ​​désolé, je veux dire que je suggère cet exemple, comme l'un des cas non rares où vous voulez vraiment un écrasement, par rapport à la comparaison dans la question où peut-être vous ne l'avez pas fait.
sourcejedi
0

La raison principale est qu'une interface graphique est par définition interactive, alors qu'un binaire /bin/cpest juste un programme qui peut être appelé depuis toutes sortes d'endroits, par exemple depuis votre interface graphique ;-). Je parie que même aujourd'hui, la grande majorité des appels vers /bin/cpne proviendront pas d'un véritable terminal avec un utilisateur tapant une commande shell, mais plutôt d'un serveur HTTP ou d'un système de messagerie ou d'un NAS. Une protection intégrée contre les erreurs des utilisateurs prend tout son sens dans un environnement interactif; moins dans un simple binaire. Par exemple, votre interface graphique appellera très probablement /bin/cpen arrière-plan pour effectuer les opérations réelles et devra traiter les questions de sécurité sur la sortie standard même si elle vient de demander à l'utilisateur!

Notez qu'il était dès le premier jour presque trivial d'écrire un wrapper sûr /bin/cpsi vous le souhaitez. La philosophie * nix est de fournir des blocs de construction simples pour les utilisateurs: l'un d'eux en /bin/cpest un.

Peter - Rétablir Monica
la source