Perché verificare l'esistenza del file prima di acquistarlo?


13

Quando si tenta di eseguire il sorgente di un file, non si desidera un errore che informi che il file non esiste in modo da sapere cosa risolvere?

Ad esempio, nvm consiglia di aggiungere questo al tuo profilo / rc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

Con sopra, se nvm.shnon esiste, otterrai un "errore silenzioso". Ma se ci provi . "$NVM_DIR/nvm.sh", l'output sarà FILE_PATH: No such file or directory.


3
Non dovresti. È audace. Prova l'origine, quindi gestisci l'errore, se presente
Mikel,

2
@Mikel giusto! allora perché lo vedo ovunque?
JBallin,

3
@FaheemMitha, beh, se quello che stai facendo è assolutamente sensibile alla sicurezza (cioè il tuo programma funziona per conto di qualcun altro), allora devi davvero preoccuparti delle condizioni di gara ( TOCTOU ). Questo probabilmente non è il caso qui, dal momento che hai maggiori problemi se qualcuno riesce a modificare i file HOME.
ilkkachu,

8
@FaheemMitha Non è mai meglio verificare prima l'esistenza. La situazione può cambiare tra test e uso, producendo sia falsi positivi che falsi negativi: e devi comunque gestire il fallimento durante l'uso. E come hai menzionato le prestazioni, i test prima dell'uso sono endemicamente due volte più lenti rispetto a non farlo e lasciare che il sistema lo faccia, cosa che farà comunque. Non può non esserlo.
user207421

1
@SimonRichter, in questo caso nvm non funzionerà. L'utente dovrebbe capire che il file manca da solo.
JBallin,

Risposte:


25

Nelle shell POSIX, .è un builtin speciale, quindi il suo errore provoca l'uscita della shell (in alcune shell come bash, viene eseguita solo in modalità POSIX).

Ciò che si qualifica come errore dipende dalla shell. Non tutti escono in seguito a un errore di sintassi durante l'analisi del file, ma la maggior parte terminerebbe quando non è possibile trovare o aprire il file di origine. Non conosco alcuno che uscirebbe se l'ultimo comando nel file di origine restituisse con uno stato di uscita diverso da zero (a meno che l' errexitopzione non sia attiva, ovviamente).

Qui facendo:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

È un caso in cui si desidera estrarre il file se è presente e non farlo se non lo è (o è vuoto qui con -s).

Cioè, non dovrebbe essere considerato un errore (errore fatale nelle shell POSIX) se il file non è presente, quel file è considerato un file opzionale.

Sarebbe comunque un errore (fatale) se il file non fosse leggibile o fosse una directory o (in alcune shell) se si fosse verificato un errore di sintassi durante l'analisi che sarebbe condizioni di errore reali che dovrebbero essere segnalate.

Alcuni sosterrebbero che esiste una condizione di razza. Ma l'unica cosa che significherebbe sarebbe che la shell sarebbe uscita con un errore se il file fosse rimosso tra il [e ., ma direi che è valido considerarlo un errore che questo file a percorso fisso svanirebbe improvvisamente mentre lo script è in esecuzione.

D'altro canto,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

dove command¹ rimuove l' attributo speciale dal .comando (quindi non esce dalla shell in caso di errore) non funzionerebbe come:

  • nasconderebbe .gli errori ma anche gli errori dei comandi eseguiti nel file di origine
  • nasconderebbe anche condizioni di errore reali come il file con autorizzazioni errate.

Altre sintassi comuni (vedere ad esempio grep -r /etc/default /etc/init*sui sistemi Debian per gli script di init che non sono stati systemdancora convertiti (dove invece EnvironmentFile=-/etc/default/serviceviene usato per specificare un file di ambiente opzionale)) includono:

  • [ -e "$file" ] && . "$file"

    Controlla che il file sia lì, procedi ancora se è vuoto. Errore fatale se non può essere aperto (anche se è lì o era lì). Potresti vedere più varianti come [ -f "$file" ](esiste ed è un file normale ), [ -r "$file" ](è leggibile) o combinazioni di quelle.

  • [ ! -e "$file" ] || . "$file"

    Una versione leggermente migliore. Rende più chiaro che il file inesistente è un caso OK. Ciò significa anche $?che rifletterà lo stato di uscita dell'ultimo comando eseguito $file(nel caso precedente, se ottieni 1, non sai se è perché $filenon esisteva o se quel comando falliva).

  • command . "$file"

    Aspettati che il file sia lì, ma non uscire se non può essere interpretato.

  • [ ! -e "$file" ] || command . "$file"

    Combinazione di quanto sopra: va bene se il file non è presente e, per le shell POSIX, vengono segnalati errori nell'apertura (o nell'analisi) del file ma non sono fatali (per cui potrebbe essere più desiderabile ~/.profile).


¹ Nota: zshtuttavia, non è possibile utilizzare in commandquesto modo se non in shemulazione; nota che nella shell Korn, in sourcerealtà è un alias per command ., una variante non speciale di.


Interessante! Non lo sapevo di POSIX sh. Ma la domanda riguardava .bash_profile. Immagino sia più sicuro che dispiaciuto, ma bash è mai in modalità POSIX quando .bash_profileproviene?
Mikel,

(Mi rendo conto che potresti interpretare questa domanda come applicabile in modo più ampio a tutte le shell POSIX basate sulla lettura del link sorgente nvm nella domanda.)
Mikel

@Mikel, la mia risposta vale ancora bashquando non si è in modalità POSIX. Vorresti [ -e /file ] && . /filese non lo considerassi un errore quando il file non esiste. L' origine try quindi gestisce l'errore, se non è possibile eseguirlo qui.
Stéphane Chazelas,

1
@Mikel, questo è controproducente. 1) che non impedisce l'uscita in caso di errore con shell POSIX (o bash in modalità POSIX) 2), che duplica il messaggio di errore (il tuo su stdout), .segnalerà già un errore (su stderr). E se l'intento è di non considerarlo un errore quando il file non esiste, non è corretto (e non è possibile, dallo stato di uscita per dire se .fallito perché il file non esisteva o non era leggibile, o era non analizzabile o l'ultimo comando non è riuscito) quali sono i punti che sto sottolineando qui in questa risposta.
Stéphane Chazelas,

1
Rispetto alle condizioni di gara - è molto meno un problema per IMHO che il login fallisca una volta (mentre qualcosa di strano sta accadendo sul sistema) che per il login fallire in modo coerente (anche se c'è qualcosa di non proprio nella configurazione dell'utente -File). Quindi una condizione di gara in questo controllo è ancora un miglioramento rispetto al non avere il controllo.
Ruakh,

5

Gestore della nvmrisposta:

è facile disinstallare nvm semplicemente cancellando il file; forzare lavoro aggiuntivo (per rintracciare dove sono le linee che fonte nvm) non sembra particolarmente prezioso.

La mia interpretazione (combinata con l'eccellente spiegazione di Stéphane e il commento di Kusalananda):

È più semplice e sicuro.

Difende dalle shell POSIX che escono all'avvio a causa di un file mancante (per vari motivi). Coloro che usano shell non POSIX (es. Bash) possono rimuovere il condizionale se preferiscono.


1
Mi oppongo alla tua interpretazione secondo cui è "rivolto ai principianti". È difensivo. Non si desidera che la shell di accesso di un utente termini in modo imprevisto all'avvio solo perché quel file è scomparso (per qualsiasi motivo). Se quella riga di codice si trova in uno dei file init della shell sotto /etc, allora consente ad alcuni utenti di avere il file e altri di non avere il file. IMHO, la nvmrisposta del manutentore tocca solo un aspetto.
Kusalananda

1

Come hanno sottolineato JBallin e Stéphane Chazelas , nelle shell POSIX, l'approvvigionamento di un file che non esiste causerebbe il fallimento dell'accesso .

Ma aggiungere un test per vedere se il file esiste e quindi provare a cercarlo può causare una condizione chiamata race condition. Se qualcosa cambia nvm.shtra il [ -s nvm.sh ]e il . nvm.sh, causerà esattamente il bug che stanno cercando di prevenire, anche se molto più raramente.

In generale, il modo per prevenire le condizioni di gara è semplicemente provare la cosa che vuoi fare, quindi gestire l'errore se fallisce, ad es.

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

Si scopre che questo non funziona nelle shell POSIX, perché, come sopra, un .errore causerà la chiusura immediata della shell, prima che possa essere eseguita qualsiasi gestione degli errori.

La mia risposta sostiene che le shell POSIX non sono rilevanti per questa domanda, perché .bash_profilenon dovrebbero mai funzionare in modalità POSIX. Quindi possiamo solo fare il codice sopra comunque.

Per sicurezza, potremmo assicurarci che la modalità POSIX non sia attiva o che la modalità POSIX sia disabilitata usando la tecnica descritta in /unix//a/383581/3169 .

La risposta di Stéphane ha alcuni suggerimenti utili su come gestire tutte le shell POSIX, che penso fosse l'intento dell'autore nvm, ma era leggermente diverso da quello che la domanda qui stava ponendo, motivo per cui abbiamo molteplici possibili approcci, a seconda di quale sia il tuo obiettivo .

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.