Perché `cp` è stato progettato per sovrascrivere silenziosamente i file esistenti? [chiuso]


30

Ho provato cpcon i seguenti comandi:

$ ls
first.html   second.html  third.html

$ cat first.html
first

$ cat second.html
second

$ cat third.html
third

Quindi copio first.htmlsu second.html:

$ cp first.html second.html

$ cat second.html
first

Il file second.htmlviene sovrascritto silenziosamente senza errori. Tuttavia, se lo faccio in una GUI desktop trascinando un file con lo stesso nome, verrà aggiunto first1.htmlautomaticamente il suffisso . Questo evita di sovrascrivere accidentalmente un file esistente.

Perché non cpsegue questo schema invece di sovrascrivere i file in silenzio?


10
Immagino che solo i designer di coreutils possano veramente rispondere alla domanda, ma per ora funziona così. Di solito le app sono costruite supponendo che l'utente significhi davvero ciò che stanno facendo e per ridurre al minimo i suggerimenti aggiuntivi. Se si desidera modificare il comportamento, alias 'cp' in 'cp ​​-i' o 'cp -n'.
kevlinux,

8
@kevlinux Gli sviluppatori coreutils stanno solo implementando lo standard POSIX.
Kusalananda

17
Perché quando è stato progettato le persone volevano essere il più concise possibile con quello che fanno (quindi non copiare) e sapevano cosa facevano e quando facevano errori non cercavano di incolpare gli strumenti. All'epoca era un tipo completamente diverso di persone che utilizzava i computer. È come chiedere perché anche un bisturi per un cardiochirurgo può tagliare le mani.
PlasmaHH

4
Unix è stato progettato da e per esperti di computer, con il presupposto che l'utente sapesse cosa stava facendo. Il sistema operativo farebbe esattamente ciò che l'utente gli ha detto, se possibile, senza tenere la mano dell'utente e senza chiedere infinite conferme. Se un'operazione sovrascriveva qualcosa, si presumeva che fosse ciò che l'utente voleva. Ricorda anche che questi erano i primi anni '70 - DOS pre-MS, Windows e home computer - che guidavano e tenevano la mano dell'utente ad ogni passo, non era ancora comune. Inoltre, con il teletipo lavorato come terminali, chiedere conferme sarebbe sempre troppo ingombrante.
Baard Kopperud,

10
Non alias cpper cp -io simili perché si abitua ad avere una rete di sicurezza, rendere i sistemi in cui non è disponibile (la maggior parte di essi) che molto più rischioso. Meglio insegnare a te stesso di routine, cp -iecc. Se è quello che preferisci.
Reid,

Risposte:


52

Il comportamento di sovrascrittura predefinito di cpè specificato in POSIX.

  1. Se source_file è di tipo file normale, devono essere eseguite le seguenti operazioni:

    3.a. Il comportamento non è specificato se esiste dest_file ed è stato scritto da un passaggio precedente. Altrimenti, se esiste dest_file, devono essere prese le seguenti misure:

    3.ai Se l'opzione -i è attiva, l'utilità cp deve scrivere un prompt per l'errore standard e leggere una riga dall'input standard. Se la risposta non è affermativa, cp non dovrà fare altro con source_file e passare a tutti i file rimanenti.

    3.a.ii. Un descrittore di file per dest_file deve essere ottenuto eseguendo azioni equivalenti alla funzione open () definita nel volume Interfacce di sistema di POSIX.1-2017 chiamato usando dest_file come argomento del percorso e OR OR-bit-bit di O_WRONLY e O_TRUNC come argomento oflag.

    3.a.iii. Se il tentativo di ottenere un descrittore di file fallisce e l'opzione -f è attiva, cp tenterà di rimuovere il file eseguendo azioni equivalenti alla funzione unlink () definita nel volume Interfacce di sistema di POSIX.1-2017 chiamata usando dest_file come argomento del percorso. Se questo tentativo ha esito positivo, cp deve continuare con il passaggio 3b.

Quando è stata scritta la specifica POSIX, esisteva già un gran numero di script, con un presupposto incorporato per il comportamento di sovrascrittura predefinito. Molti di questi script sono stati progettati per essere eseguiti senza la presenza diretta dell'utente, ad es. Come cron job o altre attività in background. Cambiare il comportamento li avrebbe spezzati. Revisionarli e modificarli tutti per aggiungere un'opzione per forzare la sovrascrittura laddove necessario era probabilmente considerato un compito enorme con benefici minimi.

Inoltre, la riga di comando di Unix è stata sempre progettata per consentire a un utente esperto di lavorare in modo efficiente, anche a scapito di una dura curva di apprendimento per un principiante. Quando l'utente immette un comando, il computer deve aspettarsi che l'utente lo intenda davvero, senza alcuna seconda ipotesi; è responsabilità dell'utente prestare attenzione con comandi potenzialmente distruttivi.

Quando fu sviluppato Unix originale, i sistemi avevano così poca memoria e memoria di massa rispetto ai computer moderni che sovrascrivevano avvertimenti e messaggi probabilmente erano visti come lussi inutili e inutili.

Durante la stesura dello standard POSIX, il precedente è stato stabilito con fermezza e gli autori dello standard erano ben consapevoli delle virtù di non rompere la compatibilità all'indietro .

Inoltre, come altri hanno descritto, qualsiasi utente può aggiungere / abilitare tali funzionalità per se stesso, usando gli alias di shell o anche creando un cpcomando di sostituzione e modificandoli $PATHper trovare la sostituzione prima del comando di sistema standard e ottenere la rete di sicurezza in questo modo se desiderato.

Ma se lo fai, scoprirai che stai creando un pericolo per te stesso. Se il cpcomando si comporta in un modo se utilizzato in modo interattivo e in un altro modo quando viene chiamato da uno script, è possibile che non si ricordi che la differenza esiste. Su un altro sistema, potresti finire per essere negligente perché ti sei abituato agli avvisi e alle istruzioni sul tuo sistema.

Se il comportamento negli script corrisponderà ancora allo standard POSIX, è probabile che ti abitui ai prompt in uso interattivo, quindi scrivi uno script che esegue una copia di massa e poi scopri di nuovo che hai inavvertitamente sovrascritto qualcosa.

Se imponi la richiesta anche negli script, cosa farà il comando quando verrà eseguito in un contesto senza utenti, ad esempio processi in background o cron job? Lo script si bloccherà, si interromperà o sovrascriverà?

Impiccagione o interruzione significa che un'attività che doveva essere eseguita automaticamente non verrà eseguita. La non sovrascrittura a volte può anche causare un problema da sola: ad esempio, potrebbe far sì che i vecchi dati vengano elaborati due volte da un altro sistema invece di essere sostituiti con dati aggiornati.

Gran parte della potenza della riga di comando deriva dal fatto che una volta che sai come fare qualcosa sulla riga di comando, implicitamente saprai anche come farlo accadere automaticamente tramite gli script . Ma questo è vero solo se i comandi che usi interattivamente funzionano esattamente allo stesso modo quando vengono invocati in un contesto di script. Qualsiasi differenza significativa nel comportamento tra l'uso interattivo e l'uso con script creerà una sorta di dissonanza cognitiva che è fastidiosa per un utente esperto.


54
"Perché funziona così?" "Perché lo standard dice così." "Perché lo standard lo dice?" "Perché ha già funzionato come questo."
Baptiste Candellier,

16
L'ultimo paragrafo è il vero motivo. Le finestre di dialogo di conferma e le istruzioni " Vuoi davvero farlo? " Sono per
WIMP

@BaptisteCandellier - Concordato. È come se la ragione ultima fosse là fuori, ma in modo allettante per la portata di questa risposta.
TED

2
L'ultimo paragrafo è il motivo rm -rfper cui è così efficace, anche se in realtà non intendevi eseguirlo nella tua home directory ...
Max Vernon

2
@TED ​​Divertente come nessuno menziona mai come anche il slinkall non collegato (2) 'non riesca' a chiedere conferma a “Madre, posso?” Ogni volta che queste discussioni semplicistiche sollevano nuovamente le loro teste delicate. :)
tchrist,

20

cpviene dall'inizio di Unix. Era lì ben prima che fosse scritto lo standard Posix. Anzi: Posix ha appena formalizzato il comportamento esistente cpal riguardo.

Stiamo parlando di Epoch (1970-01-01), quando gli uomini erano veri uomini, le donne erano vere donne e piccole creature pelose ... (sto divagando). A quei tempi, l'aggiunta di un codice aggiuntivo rendeva un programma più grande. Quello era un problema allora, perché il primo computer che eseguiva Unix era un PDP-7 (aggiornabile a 144 KB di RAM!). Quindi le cose erano piccole, efficienti, senza caratteristiche di sicurezza.

Quindi, a quei tempi, dovevi sapere cosa stavi facendo, perché il computer non aveva il potere di impedirti di fare qualsiasi cosa di cui ti pentissi in seguito.

(C'è un simpatico cartone animato di Zevar; cerca "zevar cerveaux assiste par ordinateur" per trovare l'evoluzione del computer. Oppure prova http://perinet.blogspirit.com/archive/2012/02/12/zevar-et- cointe.html finché esiste)

Per quelli veramente interessati (ho visto alcune speculazioni nei commenti): L'originale cpsul primo Unix era di circa due pagine di codice assembler (la C è arrivata dopo). La parte rilevante era:

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

(Quindi, un duro sys creat)

E, mentre ci siamo: viene utilizzata la versione 2 di Unix (codice sniplet)

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

che è anche un duro creatsenza test o garanzie. Si noti che il codice C per V2 Unix di cpè inferiore a 55 righe!


5
Quasi corretto, excepr è " piccolo peloso " (creature di Alpha Centauri) non " piccolo peloso "!
TripeHound,

1
@TED: è del tutto possibile le prime versioni di cpappena opened la destinazione con O_CREAT | O_TRUNCed eseguito un read/ writeloop; certo, con il moderno cpci sono così tante manopole che in sostanza deve provare prima a statdestinazione e potrebbe facilmente verificare prima l'esistenza (e lo fa con cp -i/ cp -n), ma se le aspettative fossero stabilite dagli strumenti originali, bare bones cp, cambiando quel comportamento romperebbe inutilmente gli script esistenti. Dopotutto, non è come se le conchiglie moderne non potessero aliassemplicemente diventare cp -ipredefinite per l'uso interattivo.
ShadowRanger

@ShadowRanger - Hmmm. Hai perfettamente ragione sul fatto che non ho davvero idea se fosse facile o difficile da fare. Commento cancellato
TED

1
@ShadowRanger Già, ma allora questo è solo spingendo la dura lezione lungo la strada fino a quando è su un sistema di produzione ...
chrylis -on strike-

1
@sourcejedi: Fun! Non cambia la mia teoria di base (che era più semplice aprire incondizionatamente con il troncamento e creatche equivale a open+ O_CREAT | O_TRUNC), ma la mancanza di O_EXCLspiega perché non sarebbe stato così facile gestire i file esistenti; provare a farlo sarebbe intrinsecamente audace (in pratica dovresti open/ statcontrollare l'esistenza, quindi utilizzare creat, ma su sistemi condivisi di grandi dimensioni, è sempre possibile prima che arrivi creat, qualcun altro ha creato il file e ora hai saltato via comunque). Può anche sovrascrivere incondizionatamente.
ShadowRanger,

19

Perché questi comandi sono anche pensati per essere utilizzati negli script, possibilmente in esecuzione senza alcun tipo di supervisione umana, e anche perché ci sono molti casi in cui si desidera davvero sovrascrivere il target (la filosofia delle shell di Linux è che l'umano sa cosa lei sta facendo)

Ci sono ancora alcune garanzie:

  • GNU cpha un -n| --no-clobberopzione
  • se copi più file in uno solo, cpti lamenterai che l'ultimo non è una directory.

Questo vale solo per un'implementazione specifica del fornitore e la domanda non riguardava l'implementazione specifica di quel fornitore.
schily

10

È "fare una cosa alla volta"?

Questo commento sembra una domanda su un principio generale di progettazione. Spesso, le domande su questi sono molto soggettive e non siamo in grado di scrivere una risposta adeguata. Tieni presente che in questo caso potremmo chiudere le domande.

A volte abbiamo una spiegazione per la scelta del design originale, perché gli sviluppatori hanno scritto su di loro. Ma non ho una risposta così bella per questa domanda.

Perché cpè progettato in questo modo?

Il problema è che Unix ha più di 40 anni.

Se stavi creando un nuovo sistema ora, potresti fare diverse scelte progettuali. Ma cambiare Unix romperebbe gli script esistenti, come menzionato in altre risposte.

Perché è stato cp progettato per sovrascrivere silenziosamente i file esistenti?

La risposta breve è "Non lo so" :-).

Comprendi che cpè solo un problema. Penso che nessuno dei programmi di comando originali sia protetto dalla sovrascrittura o dall'eliminazione dei file. La shell ha un problema simile quando reindirizza l'output:

$ cat first.html > second.html

Questo comando inoltre sovrascrive silenziosamente second.html.

Sono interessato a pensare a come riprogettare tutti questi programmi. Potrebbe richiedere ulteriore complessità.

Penso che questo sia parte della spiegazione: i primi Unix enfatizzavano semplici implementazioni . Per una spiegazione più dettagliata di ciò, vedi "peggio è meglio", collegato alla fine di questa risposta.

È possibile modificare in > second.htmlmodo che si interrompa con un errore, se second.htmlesiste già. Tuttavia, come abbiamo accennato, a volte l'utente non vuole sostituire un file esistente. Ad esempio, potrebbe creare un comando complesso, provando più volte finché non fa quello che vuole.

L'utente potrebbe eseguire rm second.htmlprima se necessario. Questo potrebbe essere un buon compromesso! Ha alcuni possibili svantaggi.

  1. L'utente deve digitare il nome file due volte.
  2. Le persone hanno anche molti problemi con l'uso rm. Quindi vorrei rendere anche rmpiù sicuro. Ma come? Se facciamo rmmostrare ogni nome di file e chiediamo all'utente di confermare, ora deve scrivere tre righe di comandi anziché uno. Inoltre, se deve farlo troppo spesso, prenderà l'abitudine e digiterà "y" per confermare senza pensare. Quindi potrebbe essere molto fastidioso e potrebbe essere ancora pericoloso.

Su un sistema moderno, consiglio di installare il trashcomando e di usarlo al posto del rmpossibile. L'introduzione della memoria Cestino è stata una grande idea, ad esempio per un PC grafico a utente singolo .

Penso che sia anche importante comprendere i limiti dell'hardware Unix originale: spazio RAM e disco limitato, output visualizzato su stampanti lente , nonché sistema e software di sviluppo.

Si noti che Unix originale non aveva il completamento della scheda , per riempire rapidamente un nome file per un rmcomando. (Inoltre, la shell Bourne originale non ha una cronologia dei comandi, ad esempio quando si utilizza il tasto freccia Su bash).

Con l'uscita della stampante, è necessario utilizzare line-based, ed. Questo è più difficile da imparare rispetto a un editor di testo visivo. Devi stampare alcune linee correnti, decidere come modificarle e digitare un comando di modifica.

Usare > second.htmlè un po 'come usare un comando in un editor di linee. L'effetto che ha dipende dallo stato corrente. (Se second.htmlesiste già, il suo contenuto verrà scartato). Se l'utente non è sicuro dello stato corrente, dovrebbe essere eseguita lso ls second.htmlprima.

"Implementazione semplice" come principio progettuale

C'è un'interpretazione popolare del design Unix, che inizia:

Il design deve essere semplice, sia nella realizzazione che nell'interfaccia. È più importante che l'implementazione sia semplice dell'interfaccia. La semplicità è la considerazione più importante in un design.

...

Gabriel ha sostenuto che "Peggio è meglio" ha prodotto un software di maggior successo rispetto all'approccio del MIT: fintanto che il programma iniziale è sostanzialmente buono, ci vorranno molto meno tempo e sforzi per implementarlo inizialmente e sarà più facile adattarsi a nuove situazioni. Il porting del software su nuove macchine, ad esempio, diventa molto più semplice in questo modo. Pertanto il suo utilizzo si diffonderà rapidamente, molto prima che un programma [migliore] abbia la possibilità di essere sviluppato e distribuito (vantaggio della prima mossa).

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


Perché sovrascrivere il target con cpun "problema"? Farlo in modo interattivo chiedere l'autorizzazione o fallire potrebbe essere un "problema" così grande.
Kusalananda

Wow grazie. integrare la linea guida: 1) Scrivere programmi che fanno una cosa e la fanno bene. 2) Fidati del programmatore.
Algebra,

2
La perdita di dati di @Kusalananda è un problema. Sono personalmente interessato a ridurre il rischio di perdere dati. Ci sono vari approcci a questo. Dire che si tratta di un problema non significa che anche le alternative non abbiano problemi.
sourcejedi,

1
@riderdragon I programmi scritti in linguaggio C spesso possono fallire in modi molto sorprendenti, perché C si fida del programmatore. Ma i programmatori non sono così affidabili. Dobbiamo scrivere strumenti molto avanzati, come valgrind , che sono necessari per cercare di trovare gli errori commessi dai programmatori. Penso che sia importante avere linguaggi di programmazione come Rust o Python o C # che provino a rafforzare la "sicurezza della memoria" senza fidarsi del programmatore. (Il linguaggio C è stato creato da uno degli autori di UNIX, al fine di scrivere UNIX in un linguaggio portatile).
sourcejedi,

1
Ancora meglio è il cat first.html second.html > first.htmlrisultato first.htmlsarà sovrascritto con il contenuto di second.htmlsolo. I contenuti originali vengono persi per sempre.
doneal24,

9

Il design di "cp" risale al design originale di Unix. C'era in effetti una filosofia coerente dietro il design di Unix, che è stato leggermente meno di quello scherzosamente definito come Peggio-è-Migliore * .

L'idea di base è che mantenere il codice semplice è in realtà una considerazione progettuale più importante che avere un'interfaccia perfetta o "fare la cosa giusta".

  • Semplicità: il design deve essere semplice, sia nella realizzazione che nell'interfaccia. È più importante che l'implementazione sia semplice dell'interfaccia . La semplicità è la considerazione più importante in un design.

  • Correttezza: il design deve essere corretto in tutti gli aspetti osservabili. È leggermente meglio essere semplici che corretti.

  • Coerenza: il design non deve essere eccessivamente incoerente. La coerenza può essere sacrificata per semplicità in alcuni casi, ma è meglio abbandonare quelle parti del progetto che affrontano circostanze meno comuni piuttosto che introdurre complessità implementativa o incoerenza.

  • Completezza: il progetto deve coprire tutte le situazioni importanti che è pratico. Tutti i casi ragionevolmente previsti dovrebbero essere coperti. La completezza può essere sacrificata a favore di qualsiasi altra qualità. In effetti, la completezza deve essere sacrificata ogni volta che viene compromessa la semplicità di implementazione. La coerenza può essere sacrificata per ottenere completezza se viene mantenuta la semplicità; particolarmente inutile è la coerenza dell'interfaccia.

( enfatizzare il mio )

Ricordando che questo era il 1970, il caso d'uso di "Voglio copiare questo file solo se non esiste già" sarebbe stato un caso d'uso abbastanza raro per qualcuno che eseguiva una copia. Se è quello che volevi, saresti abbastanza in grado di controllare prima della copia, e questo può anche essere scritto.

Per quanto riguarda il motivo per cui un sistema operativo con quell'approccio progettuale è stato quello che ha vinto su tutti gli altri sistemi operativi in ​​quel momento, l'autore del saggio aveva una teoria anche per questo.

Un ulteriore vantaggio della filosofia peggiore è che il programmatore è condizionato a sacrificare un po 'di sicurezza, convenienza e seccatura per ottenere buone prestazioni e un uso modesto delle risorse. I programmi scritti usando l'approccio del New Jersey funzioneranno bene sia su macchine piccole che grandi, e il codice sarà portatile perché è scritto sopra un virus.

È importante ricordare che il virus iniziale deve essere sostanzialmente buono. In tal caso, la diffusione virale è assicurata finché è portatile. Una volta che il virus si è diffuso, ci sarà la pressione per migliorarlo, possibilmente aumentando la sua funzionalità più vicino al 90%, ma gli utenti sono già stati condizionati ad accettare peggio della cosa giusta. Pertanto, prima il software peggio è meglio otterrà l'accettazione, il secondo condizionerà i suoi utenti ad aspettarsi di meno e il terzo verrà migliorato fino a un punto che è quasi la cosa giusta.

* - o quello che l'autore, ma nessun altro, ha chiamato "L'approccio del New Jersey" .


1
Questa è la risposta esatta.
tchrist,

+1, ma penso che sarebbe utile avere un esempio concreto. Quando installi una nuova versione di un programma che hai modificato e ricompilato (e forse testato :-), vuoi deliberatamente sovrascrivere la vecchia versione del programma. (E probabilmente si desidera un comportamento simile per il vostro compilatore. Così presto UNIX ha solo creat()vs open(). open()Impossibile creare un file se non esistesse. Ci vogliono solo 0/1/2 per la lettura / scrittura / entrambi. Non ancora prendere O_CREAT, e non c'è O_EXCL).
sourcejedi,

@sourcejedi - Siamo spiacenti, ma come sviluppatore di software, onestamente non riesco a pensare a un altro scenario diverso da quello in cui farei una copia. :-)
TED

@TED ​​scusami, intendo che sto suggerendo questo esempio, come uno dei casi non rari in cui vuoi assolutamente sovrascrivere, rispetto al confronto nella domanda in cui forse non l'hai fatto.
sourcejedi,

0

Il motivo principale è che una GUI è per definizione interattiva, mentre un binario come /bin/cpè solo un programma che può essere chiamato da tutti i tipi di luoghi, ad esempio dalla tua GUI ;-). Scommetto che anche oggi la stragrande maggioranza delle chiamate /bin/cpnon proviene da un vero terminale con un utente che digita un comando shell ma piuttosto da un server HTTP o un sistema di posta o un NAS. Una protezione integrata contro gli errori degli utenti ha perfettamente senso in un ambiente interattivo; meno in un semplice binario. Ad esempio, molto probabilmente la tua GUI chiamerà /bin/cpin background per eseguire le operazioni effettive e dovrebbe affrontare le domande di sicurezza su standard out anche se ha appena chiesto all'utente!

Si noti che era dal primo giorno quasi banale scrivere un wrapper sicuro /bin/cpse lo si desidera. La filosofia * nix è quella di fornire semplici elementi costitutivi per gli utenti: di questi, /bin/cpè uno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.