Also habe ich meinen Home-Ordner (oder genauer gesagt alle Dateien, auf die ich Schreibzugriff hatte) gelöscht. Was passiert ist, dass ich hatte
build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>
in einem Bash-Skript und nachdem Sie $build
die Deklaration und alle ihre Verwendungen nicht mehr benötigt haben , entfernen Sie sie - aber die rm
. Bash dehnt sich glücklich aus rm -rf /*
. Ja.
Ich fühlte mich dumm, habe das Backup installiert, die Arbeit, die ich verloren habe, überarbeitet. Der Versuch, die Schande hinter sich zu lassen.
Nun frage ich mich: Was sind Techniken, um Bash-Skripte so zu schreiben, dass solche Fehler nicht auftreten können oder zumindest weniger wahrscheinlich sind? Hatte ich zum Beispiel geschrieben
FileUtils.rm_rf("#{build}/*")
In einem Ruby-Skript hätte sich der Dolmetscher darüber beschwert, dass er build
nicht deklariert wurde, daher schützt mich die Sprache.
Was ich in bash außer dem Korralieren betrachtet habe rm
(was, wie viele Antworten in verwandten Fragen erwähnen, nicht unproblematisch ist):
rm -rf "./${build}/"*
Das hätte meine aktuelle Arbeit (ein Git-Repo) getötet, aber sonst nichts.- Eine Variante / Parametrisierung
rm
davon erfordert Interaktion, wenn Sie außerhalb des aktuellen Verzeichnisses agieren. (Konnte keine finden.) Ähnliche Wirkung.
Ist es das oder gibt es andere Möglichkeiten, Bash-Skripte zu schreiben, die in diesem Sinne "robust" sind?
quelle
rm -rf "${build}/*
wohin die Anführungszeichen gehen.rm -rf "${build}
wird das gleiche wegen der tunf
.set -u
ist beiset -e
weitem nicht so verpönt wie es ist, aber es hat immer noch seine Fallstricke.#! /usr/bin/env ruby
oben in jedes shell script schreiben und bash vergessen;)Antworten:
oder
Dies würde die aktuelle Shell veranlassen, Erweiterungen nicht festgelegter Variablen als Fehler zu behandeln:
set -u
undset -o nounset
sind POSIX-Shell-Optionen .Ein leerer Wert würde jedoch keinen Fehler auslösen.
Verwenden Sie dazu
Die Erweiterung von
${variable:?word}
würde sich auf den Wert von erweitern, esvariable
sei denn, sie ist leer oder nicht gesetzt. Wenn es leer oder nicht gesetzt ist, wird derword
Standardfehler angezeigt und die Shell behandelt die Erweiterung als Fehler (der Befehl wird nicht ausgeführt, und wenn er in einer nicht interaktiven Shell ausgeführt wird, wird dies beendet). Wenn Sie das:
Feld verlassen, wird der Fehler nur für einen nicht festgelegten Wert ausgelöst, genau wie unterset -u
.${variable:?word}
ist eine POSIX-Parametererweiterung .Beides würde nicht dazu führen, dass eine interaktive Shell beendet wird, es sei denn,
set -e
(oderset -o errexit
) ist ebenfalls in Kraft.${variable:?word}
Bewirkt, dass Skripte beendet werden, wenn die Variable leer oder nicht gesetzt ist.set -u
würde dazu führen, dass ein Skript beendet wird, wenn es zusammen mit verwendet wirdset -e
.Wie für Ihre zweite Frage. Es gibt keine Möglichkeit, die
rm
Arbeit außerhalb des aktuellen Verzeichnisses zu beschränken.Die GNU-Implementierung von
rm
hat eine--one-file-system
Option, die das rekursive Löschen von gemounteten Dateisystemen verhindert, aber das ist so nah, wie wir glauben, ohne denrm
Aufruf in eine Funktion zu packen, die die Argumente tatsächlich prüft.Als Randnotiz:
${build}
Entspricht genau,$build
wenn die Erweiterung nicht als Teil einer Zeichenfolge erfolgt, bei der das unmittelbar folgende Zeichen ein gültiges Zeichen in einem Variablennamen ist, z. B. in"${build}x"
.quelle
${build:?msg}
oder${build?msg}
? 2) Im Zusammenhang mit so etwas wie Build-Skripten wäre es meiner Meinung nach in Ordnung, ein anderes Tool als rm für das sichere Löschen zu verwenden: Wir wissen, dass wir nicht außerhalb des aktuellen Verzeichnisses arbeiten möchten, daher machen wir dies explizit durch die Verwendung eines eingeschränkten Befehl. Es besteht keine Notwendigkeit, mich allgemein sicherer zu machen.:
(es würde den Fehler nur für nicht gesetzte Variablen auslösen ). Ich wage nicht zu versuchen, ein Tool zu schreiben, das unter allen Umständen das Löschen von Dateien ausschließlich unter einem bestimmten Pfad bewältigt. Das Parsen von Pfaden und das Sorgen / Nicht-Sorgen um symbolische Verknüpfungen usw. ist mir im Moment etwas zu umständlich.Ich werde normale Validierungsprüfungen mit
test
/ vorschlagen.[ ]
Sie wären in Sicherheit, wenn Sie Ihr Skript als solches geschrieben hätten:
Das
[ -n "${build}" ]
prüft, dass"${build}"
eine Zeichenfolge ungleich Null ist .Dies
||
ist der logische ODER-Operator in der Bash. Wenn der erste Befehl fehlschlägt, wird ein anderer Befehl ausgeführt.Auf diese Weise
${build}
war leer / undefiniert / etc. Das Skript wurde beendet (mit dem Rückkehrcode 1, was ein allgemeiner Fehler ist).Dies hätte Sie auch geschützt, falls Sie alle Verwendungen entfernt hätten,
${build}
da dies[ -n "" ]
immer falsch ist.Der Vorteil der Verwendung von
test
/[ ]
besteht darin, dass es viele andere aussagekräftigere Prüfungen gibt, die es auch verwenden kann.Zum Beispiel:
quelle
${variable:?word}
@Kusalananda es vorschlägt?test
(dh[
) hat viele andere Überprüfungen, die relevant sind, wie-d
(das Argument ist ein Verzeichnis),-f
(das Argument ist eine Datei),-O
(die Datei gehört dem aktuellen Benutzer) und so weiter.${build:?word}
. Das${variable:?word}
Format schützt Sie nicht, wenn Sie alle Instanzen der Variablen entfernen.if
nicht ist. 2) "hättest du nicht auch $ {build:? Word} mitgenommen" - das Szenario ist, dass ich eine Verwendung der Variablen verpasst habe. Die Verwendung${v:?w}
hätte mich vor Beschädigungen geschützt. Hätte ich alle Verwendungen entfernt, wäre natürlich auch der einfache Zugriff nicht schädlich gewesen.In Ihrem speziellen Fall habe ich in der Vergangenheit das Löschen überarbeitet, um stattdessen Dateien / Verzeichnisse zu verschieben (vorausgesetzt, / tmp befindet sich auf derselben Partition wie Ihr Verzeichnis):
Hinter den Kulissen werden dabei die Dateiverweise der obersten Ebene von den Quell- zu den Zielverzeichnisstrukturen
$trashdir
auf derselben Partition verschoben , und es wird keine Zeit darauf verwendet, die Verzeichnisstruktur zu durchsuchen und die Plattenblöcke pro Datei sofort freizugeben . Dies führt zu einer viel schnelleren Bereinigung, während das System aktiv ist, im Austausch für einen etwas langsameren Neustart (/ tmp wird beim Neustart bereinigt).Alternativ verhindert ein Cron-Eintrag zum periodischen Bereinigen von /tmp/.trash-$USER, dass / tmp für Prozesse (z. B. Builds), die viel Speicherplatz verbrauchen, voll wird. Wenn sich Ihr Verzeichnis auf einer anderen Partition als / tmp befindet, können Sie ein ähnliches / tmp-ähnliches Verzeichnis auf Ihrer Partition erstellen und cron das stattdessen bereinigen lassen.
Am wichtigsten ist jedoch, dass Sie, wenn Sie die Variablen auf irgendeine Weise vermasseln, den Inhalt wiederherstellen können, bevor die Bereinigung stattfindet.
quelle
/tmp
es sich auf einer anderen Partition befindet (ich denke, es ist immer etwas für mich, zumindest für Skripte, die im Benutzerbereich ausgeführt werden). Dann muss der Papierkorb geändert werden (und profitiert nicht von der Handhabung des Betriebssystems/tmp
).mktemp -d
, um dieses temporäre Verzeichnis abzurufen, ohne auf die Zehen eines anderen Prozesses zu treten (und um es richtig zu würdigen$TMPDIR
).Verwenden Sie die Bash-Parameter-Ersetzung, um Standardeinstellungen zuzuweisen, wenn die Variable nicht initialisiert ist. Beispiel:
rm -rf $ {variable: - "/ nonexistent"}
quelle
Ich versuche immer, meine Bash-Skripte mit einer Linie zu beginnen
#!/bin/bash -ue
.-e
Mittel „auf ersten uncathed versagen e rror“;-u
bedeutet „auf der ersten Nutzung nicht von u variable ndeclared“.Weitere Details finden Sie in einem großartigen Artikel. Verwenden Sie den inoffiziellen Bash-Strict-Modus (es sei denn, Sie lieben das Debuggen) . Auch der Autor empfiehlt die Verwendung,
set -o pipefail; IFS=$'\n\t'
aber für meine Zwecke ist dies übertrieben.quelle
Der allgemeine Rat, um zu überprüfen, ob Ihre Variable festgelegt ist, ist ein nützliches Werkzeug, um solche Probleme zu vermeiden. In diesem Fall gab es jedoch eine einfachere Lösung.
Es ist höchstwahrscheinlich nicht erforderlich, den Inhalt des
$build
Verzeichnisses zu glätten , um sie zu löschen, nicht jedoch das eigentliche$build
Verzeichnis. Wenn Sie also das Fremde überspringen, wird*
ein nicht festgelegter Wertrm -rf /
angezeigt, zu dem sich die meisten RM-Implementierungen im letzten Jahrzehnt standardmäßig verweigern (es sei denn, Sie deaktivieren diesen Schutz mit der GNU RM---no-preserve-root
Option).Das Überspringen des Trailings
/
würde zu folgenderrm ''
Fehlermeldung führen:Dies funktioniert auch dann, wenn Ihr Befehl rm keinen Schutz für implementiert
/
.quelle
Sie denken in Programmiersprachen, aber bash ist eine Skriptsprache :-) Verwenden Sie also ein völlig anderes Anweisungsparadigma für ein völlig anderes Sprachparadigma .
In diesem Fall:
Da
rmdir
das Entfernen eines nicht leeren Verzeichnisses verweigert wird, müssen Sie zuerst die Mitglieder entfernen. Sie wissen, was diese Mitglieder sind, richtig? Sie sind wahrscheinlich Parameter für Ihr Skript oder von einem Parameter abgeleitet.Wenn Sie nun einige andere Dateien oder Verzeichnisse wie eine temporäre Datei oder etwas, das Sie nicht haben sollten, dort ablegen,
rmdir
wird ein Fehler ausgegeben . Behandle es richtig, dann:Diese Art zu denken hat mir gute Dienste geleistet, weil ... ja, ich war dort und habe das getan.
quelle
make clean
verwendet einige Platzhalter (es sei denn, ein sehr sorgfältiger Compiler erstellt eine Liste aller von ihm erstellten Dateien). Außerdem scheint es mir, dassrm -rf ${build}/${parameter}
das Problem nur ein wenig nach unten verschoben wird.make
in der Frage. Eine Antwort zu schreiben, die nur auf eine zutreffende Antwort zutrifft,make
wäre eine sehr spezifische und überraschende Annahme. Meine Antwort funktioniert auch in anderen Fällenmake
als bei der Softwareentwicklung, mit Ausnahme Ihres speziellen Anfängerproblems, bei dem Sie Ihre Daten gelöscht haben.