Warum sollte vor der Beschaffung geprüft werden, ob Dateien vorhanden sind?

13

Wenn Sie versuchen, eine Datei als Quelle zu verwenden, möchten Sie nicht einen Fehler erhalten, der besagt, dass die Datei nicht vorhanden ist, damit Sie wissen, was zu beheben ist?

Zum Beispiel empfiehlt nvm , dies zu Ihrem Profil / rc hinzuzufügen:

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

Wenn oben nvm.shnicht vorhanden, wird ein "stiller Fehler" angezeigt. Aber wenn Sie es versuchen . "$NVM_DIR/nvm.sh", wird die Ausgabe sein FILE_PATH: No such file or directory.

JBallin
quelle
3
Das solltest du nicht. Es ist rassig. Probieren Sie die Quelle aus, und beheben Sie gegebenenfalls den Fehler
Mikel
2
@Mikel richtig! warum sehe ich es dann überall?
JBallin
3
@FaheemMitha, na ja, wenn das, was Sie tun, überhaupt sicherheitsrelevant ist (dh Ihr Programm arbeitet im Auftrag eines anderen), müssen Sie sich wirklich um die Rennbedingungen ( TOCTOU ) kümmern . Dies ist hier jedoch wahrscheinlich nicht der Fall, da Sie größere Probleme haben, wenn jemand Dateien ändern kann HOME.
Ilkkachu
8
@FaheemMitha Es ist nie besser, zuerst auf Existenz zu prüfen. Die Situation kann sich zwischen Test und Verwendung ändern und sowohl falsch-positive als auch falsch-negative Ergebnisse liefern: Sie müssen den Fehler bei der Verwendung trotzdem behandeln. Und wie Sie bereits erwähnt haben, ist das Testen vor der Verwendung endemisch doppelt so langsam wie das Nicht-Testen. Lassen Sie das System es tun, was es auch tun wird. Es kann nicht nicht sein.
user207421
1
@ SimonRichter, in diesem Fall funktioniert nvm nicht. Benutzer müsste herausfinden, dass die Datei selbst fehlt.
JBallin

Antworten:

25

Bei POSIX-Shells .handelt es sich um eine spezielle Funktion, bei deren Fehlschlagen die Shell beendet wird (bei einigen Shells bashwird dies nur im POSIX-Modus ausgeführt).

Was als Fehler zu qualifizieren ist, hängt von der Shell ab. Nicht alle werden bei einem Syntaxfehler beim Parsen der Datei beendet, die meisten werden jedoch beendet, wenn die Quelldatei nicht gefunden oder geöffnet werden kann. Ich kenne keine, die beendet würden, wenn der letzte Befehl in der Quelldatei mit einem Beendigungsstatus ungleich Null zurückgegeben würde (es sei denn, die errexitOption ist natürlich aktiviert).

Hier machen:

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

Ist ein Fall, in dem Sie die Datei als Quelle angeben möchten, wenn sie vorhanden ist, und nicht, wenn sie nicht vorhanden ist (oder hier mit leer ist -s).

Das heißt, es sollte nicht als Fehler betrachtet werden (schwerwiegender Fehler in POSIX-Shells). Wenn die Datei nicht vorhanden ist, wird diese Datei als optionale Datei betrachtet.

Es wäre immer noch ein (schwerwiegender) Fehler, wenn die Datei nicht lesbar oder ein Verzeichnis wäre, oder (in einigen Shells), wenn beim Parsen ein Syntaxfehler aufgetreten wäre, der echte Fehlerbedingungen darstellen würde, die gemeldet werden sollten.

Einige würden argumentieren, dass es eine Rennbedingung gibt. Das einzige, was dies bedeutet, ist, dass die Shell mit einem Fehler beendet wird, wenn die Datei zwischen [und entfernt wird. wird. Ich würde jedoch argumentieren, dass es sich um einen Fehler handelt, bei dem diese Datei mit festem Pfad plötzlich verschwindet, während das Skript ausgeführt wird Laufen.

Auf der anderen Seite,

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

Wobei command¹ das spezielle Attribut für den .Befehl entfernt (damit die Shell bei einem Fehler nicht beendet wird), funktioniert nicht wie folgt:

  • es würde .die Fehler verbergen , aber auch die Fehler der Befehle, die in der Quelldatei ausgeführt werden
  • Es würde auch echte Fehlerbedingungen verbergen, wie die Datei mit den falschen Berechtigungen.

Andere gebräuchliche Syntaxen (siehe zum Beispiel grep -r /etc/default /etc/init*auf Debian-Systemen für die Init-Skripte, die noch nicht konvertiert wurden systemd(wobei EnvironmentFile=-/etc/default/servicestattdessen eine optionale Umgebungsdatei angegeben wird)):

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

    Überprüfen Sie die Datei, die sie enthält, und geben Sie sie weiterhin aus, wenn sie leer ist. Immer noch schwerwiegender Fehler, wenn es nicht geöffnet werden kann (obwohl es da ist oder war). Möglicherweise sehen Sie weitere Varianten wie [ -f "$file" ](existiert und ist eine reguläre Datei), [ -r "$file" ](ist lesbar) oder Kombinationen davon.

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

    Eine etwas bessere Version. Verdeutlicht, dass es sich bei der nicht vorhandenen Datei um einen OK-Fall handelt. Das bedeutet $?auch, dass der Exit-Status des zuletzt ausgeführten Befehls angezeigt wird $file(im vorherigen Fall 1wissen Sie nicht, ob der $fileBefehl nicht vorhanden war oder fehlgeschlagen ist).

  • command . "$file"

    Erwarten Sie, dass die Datei dort ist, aber beenden Sie sie nicht, wenn sie nicht interpretiert werden kann.

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

    Kombination der oben genannten Punkte: Wenn die Datei nicht vorhanden ist, ist dies in Ordnung. Bei POSIX-Shells werden Fehler beim Öffnen (oder Parsen) der Datei gemeldet, die jedoch nicht schwerwiegend sind (was für möglicherweise wünschenswerter ist ~/.profile).


¹ Hinweis: In zshkönnen Sie dies jedoch nur commandin der shEmulation verwenden. Beachten Sie, dass in der Korn-Shell sourcetatsächlich ein Alias ​​für command .eine nicht spezielle Variante von.

Stéphane Chazelas
quelle
Interessant! Ich wusste das nicht über POSIX sh. Aber die Frage war ungefähr .bash_profile. Ich denke, es ist sicherer als leid, aber ist die Bash jemals im POSIX-Modus, wenn sie .bash_profilebezogen wird?
Mikel
(Mir ist klar, dass Sie diese Frage dahingehend interpretieren können, dass sie auf alle POSIX-Shells angewendet wird, wenn Sie den NVM-Quelllink in der Frage lesen.)
Mikel,
@Mikel, meine Antwort gilt immer noch, bashwenn Sie sich nicht im POSIX-Modus befinden. Sie möchten, [ -e /file ] && . /filedass es sich nicht um einen Fehler handelt, wenn die Datei nicht vorhanden ist. Die try-Quelle behandelt dann den Fehler, falls dies hier nicht möglich ist.
Stéphane Chazelas
1
@Mikel, das ist kontraproduktiv. 1) Dies verhindert nicht das Beenden bei Fehlern mit POSIX-Shells (oder Bash im POSIX-Modus). 2) Das Duplizieren der Fehlermeldung (Ihre auf stdout) .meldet bereits einen Fehler (auf stderr). Und wenn die Absicht besteht, es nicht als Fehler zu betrachten, wenn die Datei nicht vorhanden ist, ist dies nicht korrekt (und es ist nicht möglich, anhand des Beendigungsstatus zu erkennen, ob ein Fehler aufgetreten ist, .weil die Datei nicht vorhanden oder nicht lesbar war oder war nicht analysierbar, oder der letzte Befehl ist fehlgeschlagen), das sind die Punkte, die ich hier in dieser Antwort mache.
Stéphane Chazelas
1
In Bezug auf die Racebedingung ist es meiner Meinung nach weitaus weniger ein Problem, wenn die Anmeldung einmal fehlschlägt (während auf dem System etwas Seltsames passiert), als dass die Anmeldung durchgehend fehlschlägt (auch wenn die Konfiguration des Benutzers etwas nicht ganz Richtiges enthält -Dateien). Eine Rennbedingung in diesem Check ist also immer noch eine Verbesserung gegenüber dem Fehlen des Checks.
Ruakh
5

Betreuer der nvmAntwort:

Es ist einfach, nvm durch einfaches Löschen der Datei zu deinstallieren. Zusätzliche Arbeit zu erzwingen (herauszufinden, wo sich die Zeile (n) in der Quell-NVM befinden) scheint nicht besonders wertvoll zu sein.

Meine Interpretation (kombiniert mit Stéphanes hervorragender Erklärung und Kusalanandas Kommentar):

Es ist einfacher und sicherer.

Es schützt vor POSIX-Shells, die beim Start aufgrund einer fehlenden Datei (aus verschiedenen Gründen) beendet werden. Benutzer von Nicht-POSIX-Shells (z. B. Bash-Shells) können die Bedingung gegebenenfalls entfernen.

JBallin
quelle
1
Ich lehne Ihre Interpretation ab, dass es "für Anfänger gedacht" ist. Es ist defensiv. Sie möchten nicht, dass die Anmeldeshell eines Benutzers beim Start unerwartet beendet wird, nur weil diese Datei verloren gegangen ist (aus welchem Grund auch immer ). Befindet sich diese Codezeile in einer der Shell-Init-Dateien unter /etc, können einige Benutzer die Datei haben und andere nicht. Meiner Meinung nach nvmberührt die Antwort des Betreuers nur einen Aspekt.
Kusalananda
1

Wie JBallin und Stéphane Chazelas in POSIX-Shells ausgeführt haben, würde die Suche nach einer Datei, die nicht vorhanden ist, zum Fehlschlagen der Anmeldung führen.

Wenn Sie jedoch einen Test hinzufügen, um festzustellen, ob die Datei vorhanden ist, und dann versuchen, sie als Quelle zu verwenden, kann dies zu einer so genannten Race Condition führen. Wenn sich etwas nvm.shzwischen dem [ -s nvm.sh ]und dem ändert . nvm.sh, verursacht dies genau den Fehler, den sie zu verhindern versuchen, wenn auch viel seltener.

Im Allgemeinen können Sie Wettkampfbedingungen verhindern, indem Sie einfach das versuchen, was Sie tun möchten, und den Fehler dann behandeln, wenn er fehlschlägt, z

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

Es stellt sich heraus, dass dies in POSIX-Shells nicht funktioniert, da .die Shell , wie oben beschrieben, beim Fehlschlagen sofort beendet wird, bevor eine Fehlerbehandlung ausgeführt werden kann.

Meine Antwort lautet, dass POSIX-Shells für diese Frage nicht relevant sind, da .bash_profilesie niemals im POSIX-Modus ausgeführt werden sollten. Also können wir den obigen Code trotzdem machen.

Um die Sicherheit zu gewährleisten, können wir mithilfe der unter /unix//a/383581/3169 beschriebenen Technik sicherstellen, dass der POSIX-Modus nicht aktiviert ist oder dass der POSIX-Modus deaktiviert ist .

Die Antwort von Stéphane enthält einige nützliche Vorschläge für den Umgang mit allen POSIX-Shells. Ich denke, das war die Absicht des Autors von nvm, aber sie unterschied sich geringfügig von der Frage, die hier gestellt wurde. Aus diesem Grund haben wir mehrere mögliche Ansätze, je nachdem, welches Ziel Sie verfolgen .

Mikel
quelle