Führen Sie mit Bash in jedem Unterverzeichnis eine Aktion aus

193

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?

Mikewilliamson
quelle
1
Bitte beachten Sie zurück kommen durch und neu zu bewerten Antworten auf Richtigkeit - Sie eine akzeptierte Antwort habe eine Menge Ansichten trotz großen Fehler (f / e bekommen, laufen sie über ein Verzeichnis , in dem jemand zuvor ran mkdir 'foo * bar'verursachen foound barsogar iteriert werden , wenn Sie existieren nicht und *werden durch eine Liste aller Dateinamen ersetzt, auch von Nicht-Verzeichnisnamen.
Charles Duffy
1
... noch schlimmer ist es, wenn jemand ausgeführt wird 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.
Charles Duffy

Antworten:

177
for D in `find . -type d`
do
    //Do whatever you need with D
done
Mike Clark
quelle
81
Dies wird auf Leerzeichen brechen
Ghostdog74
2
Beachten Sie außerdem, dass Sie die findParameter anpassen müssen, wenn Sie rekursives oder nicht rekursives Verhalten wünschen.
Chris Tonkinson
32
Die obige Antwort gab mir auch das Selbstverzeichnis, so dass das Folgende ein bisschen besser für mich find . -mindepth 1 -type d
funktionierte
1
@JoshC, beide Varianten teilen das von erstellte Verzeichnis mkdir 'directory name with spaces'in vier separate Wörter auf.
Charles Duffy
4
Ich musste hinzufügen -mindepth 1 -maxdepth 1oder es ging zu tief.
ScrappyDev
281

Eine Version, die das Erstellen eines Unterprozesses vermeidet:

for D in *; do
    if [ -d "${D}" ]; then
        echo "${D}"   # your processing here
    fi
done

Wenn Ihre Aktion ein einzelner Befehl ist, ist dies prägnanter:

for D in *; do [ -d "${D}" ] && my_command; done

Oder eine noch prägnantere Version ( danke @enzotib ). Beachten Sie, dass in dieser Version jeder Wert von Deinen abschließenden Schrägstrich enthält:

for D in */; do my_command; done
Kanaka
quelle
48
Sie können das ifoder [mit vermeiden :for D in */; do
Enzotib
2
+1, weil Verzeichnisnamen nicht mit ./ beginnen, sondern mit einer akzeptierten Antwort
Hayri Uğur Koltuk,
3
Dieser ist sogar bis zu Leerzeichen in den Verzeichnisnamen +1
Alex Reinking
5
Beim letzten Befehl gibt es ein Problem: Wenn Sie sich in einem Verzeichnis ohne Unterverzeichnisse befinden; es wird "* /" zurückgeben. Verwenden Sie also besser den zweiten Befehl for D in *; do [ -d "${D}" ] && my_command; doneoder eine Kombination der beiden neuesten:for D in */; do [ -d $D ] && my_command; done
Chris Maes
5
Beachten Sie, dass diese Antwort versteckte Verzeichnisse ignoriert. Verwenden Sie for D in .* *; dostattdessen versteckte Verzeichnisse for D in *; do.
patryk.beza
105

Der einfachste nicht rekursive Weg ist:

for d in */; do
    echo "$d"
done

Das /am Ende sagt, nur Verzeichnisse verwenden.

Es besteht keine Notwendigkeit für

  • finden
  • awk
  • ...
d0x
quelle
11
Hinweis: Dies schließt keine Punktverzeichnisse ein (was eine gute Sache sein kann, aber es ist wichtig zu wissen).
wisbucky
Nützlich zu beachten, dass Sie shopt -s dotglobbeim Erweitern von Platzhaltern Dotfiles / Dotdirs einschließen können. Siehe auch: gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Steen Schütt
Ich glaube , Sie bedeutete /*statt */mit , /die den Pfad , den Sie verwenden möchten.
Brōtsyorfuzthrāx
2
@Shule /*wäre für den absoluten Pfad, während */die Unterverzeichnisse vom aktuellen Speicherort enthalten wären
Dan G
3
hilfreicher Hinweis: Wenn Sie das nachfolgende '/' von $ d ${d%/*}
kürzen müssen
18

Verwenden findBefehl.

In GNU findkönnen Sie folgende -execdirParameter verwenden:

find . -type d -execdir realpath "{}" ';'

oder mit -execParameter:

find . -type d -exec sh -c 'cd -P "$0" && pwd -P' {} \;

oder mit xargsBefehl:

find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'

Oder mit for- Schleife:

for d in */; { echo "$d"; }

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

Kenorb
quelle
1
-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 '...' {} \;.
Charles Duffy
13

Handliche Einzeiler

for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A

find * -type d ### Option B

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.

# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real    0m0.327s
user    0m0.084s
sys     0m0.236s

# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real    0m0.787s
user    0m0.484s
sys     0m0.308s
Sriram Murali
quelle
10

find . -type d -print0 | xargs -0 -n 1 my_command

Paul Tomblin
quelle
Kann ich den Rückgabewert dieses Funds in eine for-Schleife einspeisen? Dies ist Teil eines größeren Skripts ...
Mikewilliamson
@ Mike, unwahrscheinlich. $? Sie erhalten wahrscheinlich eher den Status des Befehls find oder des Befehls xargs als my_command.
Paul Tomblin
7

Dadurch wird eine Unterschale erstellt (was bedeutet, dass beim Beenden der whileSchleife Variablenwerte verloren gehen ):

find . -type d | while read -r dir
do
    something
done

Dies wird nicht:

while read -r dir
do
    something
done < <(find . -type d)

Beides funktioniert, wenn die Verzeichnisnamen Leerzeichen enthalten.

Bis auf weiteres angehalten.
quelle
Verwenden Sie find ... -print0undwhile IFS="" read -r -d $'\000' dir
Gordon Davisson
@GordonDavisson, ... in der Tat würde ich sogar argumentieren, dass die -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).
Charles Duffy
5

Du könntest es versuchen:

#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/

for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
  cd "$f"
  <your job here>
done

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 .).

Henry Dobson
quelle
Dies ist fehlerhaft - versuchen Sie es mit einem Verzeichnisnamen mit Leerzeichen. Siehe BashPitfalls # 1 und DontReadLinesWithFor .
Charles Duffy
Der Verzeichnisname mit Leerzeichen wird in Anführungszeichen gesetzt und funktioniert daher. OP versucht nicht, Zeilen aus der Datei zu lesen.
Henry Dobson
es funktioniert in der cd "$f". Es funktioniert nicht, wenn die Ausgabe von findString-Split ist. Sie haben also die einzelnen Teile des Namens als separate Werte in $f, um zu bestimmen, wie gut Sie die $fErweiterung in Frage stellen oder nicht .
Charles Duffy
Ich habe nicht gesagt , sie wurden versucht , Zeilen aus einer Datei zu lesen. findDie Ausgabe ist zeilenorientiert (theoretisch ein Name für eine Zeile - siehe unten) mit der Standardaktion -print.
Charles Duffy
Die zeilenorientierte Ausgabe von find -printist kein sicherer Weg, um beliebige Dateinamen zu übergeben, da man etwas ausführen mkdir -p $'foo\n/etc/passwd\nbar'und ein Verzeichnis abrufen kann, /etc/passwddessen Name eine separate Zeile enthält. Der sorgfältige Umgang mit Namen aus Dateien in /uploadoder /tmpVerzeichnissen ist eine hervorragende Möglichkeit, Angriffe auf die Eskalation von Berechtigungen zu erhalten.
Charles Duffy
4

Die akzeptierte Antwort wird in Leerzeichen unterbrochen, wenn die Verzeichnisnamen diese enthalten, und die bevorzugte Syntax lautet $()bash / ksh. Verwenden Sie die GNU find -exec Option mit +;z

find .... -exec mycommand +; #this is same as passing to xargs

oder verwenden Sie eine while-Schleife

find .... |  while read -r D
do
  ...
done 
Ghostdog74
quelle
Welcher Parameter enthält den Verzeichnisnamen? zB chmod + x $ DIR_NAME (ja, ich weiß, es gibt eine chmod-Option nur für Verzeichnisse)
Mike Graf
Es gibt einen subtilen Unterschied zwischen find -execund Übergabe an xargs: findignoriert den Exit-Wert des ausgeführten Befehls, während er xargsbei einem Exit ungleich Null fehlschlägt. Beides kann je nach Ihren Anforderungen richtig sein.
Jason Wodicka
find ... -print0 | while IFS= read -r dist sicherer - unterstützt Namen, die im Leerzeichen beginnen oder enden, und Namen, die Zeilenumbruchliterale enthalten.
Charles Duffy