Ich arbeite an einem Skript, das in jedem Unterverzeichnis eines bestimmten Ordners eine Aktion ausführen muss.
Was ist der effizienteste Weg, das zu schreiben?
bash
command
directory-traversal
Mikewilliamson
quelle
quelle
mkdir 'foo * bar'
verursachenfoo
undbar
sogar iteriert werden , wenn Sie existieren nicht und*
werden durch eine Liste aller Dateinamen ersetzt, auch von Nicht-Verzeichnisnamen.mkdir -p '/tmp/ /etc/passwd /'
- wenn jemand ein Skript ausführt, das dieser Vorgehensweise folgt/tmp
, um beispielsweise zu löschende Verzeichnisse zu finden, kann dies zum Löschen führen/etc/passwd
.Antworten:
quelle
find
Parameter anpassen müssen, wenn Sie rekursives oder nicht rekursives Verhalten wünschen.find . -mindepth 1 -type d
mkdir 'directory name with spaces'
in vier separate Wörter auf.-mindepth 1 -maxdepth 1
oder es ging zu tief.Eine Version, die das Erstellen eines Unterprozesses vermeidet:
Wenn Ihre Aktion ein einzelner Befehl ist, ist dies prägnanter:
Oder eine noch prägnantere Version ( danke @enzotib ). Beachten Sie, dass in dieser Version jeder Wert von
D
einen abschließenden Schrägstrich enthält:quelle
if
oder[
mit vermeiden :for D in */; do
for D in *; do [ -d "${D}" ] && my_command; done
oder eine Kombination der beiden neuesten:for D in */; do [ -d $D ] && my_command; done
for D in .* *; do
stattdessen versteckte Verzeichnissefor D in *; do
.Der einfachste nicht rekursive Weg ist:
Das
/
am Ende sagt, nur Verzeichnisse verwenden.Es besteht keine Notwendigkeit für
quelle
shopt -s dotglob
beim Erweitern von Platzhaltern Dotfiles / Dotdirs einschließen können. Siehe auch: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html/*
statt*/
mit ,/
die den Pfad , den Sie verwenden möchten./*
wäre für den absoluten Pfad, während*/
die Unterverzeichnisse vom aktuellen Speicherort enthalten wären${d%/*}
Verwenden
find
Befehl.In GNU
find
können Sie folgende-execdir
Parameter verwenden:oder mit
-exec
Parameter:oder mit
xargs
Befehl:Oder mit for- Schleife:
Versuchen Sie für Rekursivität
**/
stattdessen erweitertes globbing ( ) (aktivieren durch :)shopt -s extglob
.Weitere Beispiele finden Sie unter: Wie gehe ich zu jedem Verzeichnis und führe einen Befehl aus? bei SO
quelle
-exec {} +
ist POSIX-spezifiziert,-exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +
ist eine weitere legale Option und ruft weniger Shells als auf-exec sh -c '...' {} \;
.Handliche Einzeiler
Option A ist für Ordner mit dazwischen liegenden Leerzeichen korrekt. Im Allgemeinen auch schneller, da nicht jedes Wort in einem Ordnernamen als separate Entität gedruckt wird.
quelle
find . -type d -print0 | xargs -0 -n 1 my_command
quelle
my_command
.Dadurch wird eine Unterschale erstellt (was bedeutet, dass beim Beenden der
while
Schleife Variablenwerte verloren gehen ):Dies wird nicht:
Beides funktioniert, wenn die Verzeichnisnamen Leerzeichen enthalten.
quelle
find ... -print0
undwhile IFS="" read -r -d $'\000' dir
-d ''
Bash-Syntax und -Fähigkeiten weniger irreführend sind, da-d $'\000'
dies (fälschlicherweise) impliziert, dass$'\000'
es sich in gewisser Weise von''
- tatsächlich könnte man leicht (und wieder fälschlicherweise) daraus schließen Bash unterstützt Zeichenfolgen im Pascal-Stil (längenspezifisch, kann NUL-Literale enthalten) anstelle von C-Zeichenfolgen (NUL-begrenzt, kann keine NULs enthalten).Du könntest es versuchen:
o.ä...
Erläuterung:
find . -maxdepth 1 -mindepth 1 -type d
: Finden Sie nur Verzeichnisse mit einer maximalen rekursiven Tiefe von 1 (nur die Unterverzeichnisse von $ 1) und einer minimalen Tiefe von 1 (ohne aktuellen Ordner.
).quelle
cd "$f"
. Es funktioniert nicht, wenn die Ausgabe vonfind
String-Split ist. Sie haben also die einzelnen Teile des Namens als separate Werte in$f
, um zu bestimmen, wie gut Sie die$f
Erweiterung in Frage stellen oder nicht .find
Die Ausgabe ist zeilenorientiert (theoretisch ein Name für eine Zeile - siehe unten) mit der Standardaktion-print
.find -print
ist kein sicherer Weg, um beliebige Dateinamen zu übergeben, da man etwas ausführenmkdir -p $'foo\n/etc/passwd\nbar'
und ein Verzeichnis abrufen kann,/etc/passwd
dessen Name eine separate Zeile enthält. Der sorgfältige Umgang mit Namen aus Dateien in/upload
oder/tmp
Verzeichnissen ist eine hervorragende Möglichkeit, Angriffe auf die Eskalation von Berechtigungen zu erhalten.Die akzeptierte Antwort wird in Leerzeichen unterbrochen, wenn die Verzeichnisnamen diese enthalten, und die bevorzugte Syntax lautet
$()
bash / ksh. Verwenden Sie dieGNU find
-exec
Option mit+;
zfind .... -exec mycommand +;
#this is same as passing to xargs
oder verwenden Sie eine while-Schleife
quelle
find -exec
und Übergabe anxargs
:find
ignoriert den Exit-Wert des ausgeführten Befehls, während erxargs
bei einem Exit ungleich Null fehlschlägt. Beides kann je nach Ihren Anforderungen richtig sein.find ... -print0 | while IFS= read -r d
ist sicherer - unterstützt Namen, die im Leerzeichen beginnen oder enden, und Namen, die Zeilenumbruchliterale enthalten.