web-dev-qa-db-fra.com

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

J'ai testé cp avec 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.html est é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.html automatiquement. Cela évite d'écraser accidentellement un fichier existant.

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

29
Calculus

Le comportement d'écrasement par défaut de cp est 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.a.i. 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 bitwise-inclusive OR 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 doit continuer 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 fonctionner sans la présence directe des utilisateurs, par exemple comme 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 original 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.

Lors de l'écriture de la norme POSIX, le précédent était fermement établi et les rédacteurs de la norme étaient bien conscients des vertus de ne pas rompre la compatibilité descendante .

De plus, comme d'autres l'ont décrit, tout utilisateur peut ajouter/activer ces fonctionnalités pour lui-même, en utilisant des alias Shell ou même en créant une commande de remplacement cp et en modifiant leur $PATH pour trouver le remplaçant avant la commande système standard et obtenir le filet de sécurité de cette façon si vous le souhaitez.

Mais si vous le faites, vous constaterez que vous créez un danger pour vous-même. Si la commande cp 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 processus d'arrière-plan ou tâches cron? Le script va-t-il se bloquer, abandonner ou écraser?

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 que une fois que vous savez comment faire quelque chose sur la ligne de commande, vous saurez également implicitement comment le faire automatiquement en scriptant . 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é.

51
telcoM

cp vient 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 (1970-01-01), 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://a54.idata.over-blog.com/2/07/ 74/62/dessins-et-bd/le-CAO-de-Zevar --- reduc.jpg tant qu'il existe)

Pour ceux vraiment intéressés (j'ai vu des spéculations dans les commentaires): L'original cp sur 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, un 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 aussi un disque dur creat sans tests ni garanties. Notez que le code C pour V2 Unix de cp est inférieur à 55 lignes!

19
Ljm Dullaart

Parce que ces commandes sont également destinées à être utilisées dans des scripts, pouvant s'exécuter sans aucune sorte de supervision humaine, et aussi parce qu'il existe 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 cp a un -n | --no-clobber option
  • si vous copiez plusieurs fichiers dans un seul cp se plaindra que le dernier n'est pas un répertoire.
17
xenoid

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 réponse aussi agréable à cette question.

Pourquoi cp est 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 étaitcp conçu pour écraser silencieusement les fichiers existants?

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

Comprenez que cp n'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: Unix précoce a souligné simple implémentations. 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.html donc ça s'arrête avec une erreur, si second.html existe déjà. Cependant, comme nous l'avons mentionné, l'utilisateur le fait veut parfois 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 exécuter rm second.html d'abord si elle 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 également beaucoup de mal à utiliser rm. Je voudrais donc aussi rendre rm plus sûr. Mais comment? Si nous faisons rm afficher chaque nom de fichier et demandons à l'utilisateur de confirmer, elle 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 encore être dangereux.

Sur un système moderne, je recommande en installant la commande trash , et en l'utilisant à la place de rm si possible. L'introduction du stockage de la corbeille était une excellente idée, par exemple pour un PC graphique mono-utilisateur .

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

Notez que Unix d'origine n'avait pas tabulation terminée , pour remplir rapidement un nom de fichier pour une commande rm. (De plus, le Bourne Shell d'origine n'a pas d'historique de commande, par exemple comme lorsque vous utilisez la touche flèche vers le haut dans bash).

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

En utilisant > second.html est un peu comme utiliser une commande dans un éditeur de ligne. Son effet dépend de l'état actuel. (Si second.html existe déjà, son contenu sera supprimé). Si l'utilisateur n'est pas sûr de l'état actuel, il doit exécuter ls ou ls second.html première.

"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 "pire est mieux" a produit un logiciel plus performant que l'approche MIT: tant que le programme initial est fondamentalement bon, il faudra beaucoup moins de temps et d'efforts pour l'implémenter initialement et il être plus facile à adapter à de 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 arrivé) ).

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

9
sourcejedi

Le design de "cp" remonte au design original d'Unix. Il y avait en fait une philosophie cohérente derrière le design Unix, qui a été légèrement moins que plaisantant à 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 l'une ou l'autre des implémentations complexité ou 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.

( accentuation mine )

En se rappelant que c'était en 1970, le cas d'utilisation de "Je veux copier ce fichier seulement s'il n'existe pas déjà" aurait été assez rare cas d'utilisation pour quelqu'un effectuant 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, a appelé "l'approche du New Jersey".

9
T.E.D.

La raison principale est que ne interface graphique est par définition interactive, tandis qu'un binaire comme /bin/cp est 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 à /bin/cp ne proviendra pas d'un vrai 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. ne protection intégrée contre les erreurs des utilisateurs a tout son sens dans un environnement interactif; moins dans un simple binaire. Par exemple, votre interface graphique appellera très probablement /bin/cp en arrière-plan pour effectuer les opérations réelles et devrait s'occuper des questions de sécurité sur la sortie standard même si cela vient de demander à l'utilisateur!

Notez qu'il était presque banal dès le premier jour d'écrire un wrapper sûr autour de /bin/cp si vous le souhaitez. La philosophie * nix consiste à fournir des blocs de construction simples pour les utilisateurs: _ [/bin/cp est une.