Wie kann man Verzeichnisse unter Linux durchlaufen?

168

Ich schreibe ein Skript in Bash unter Linux und muss alle Unterverzeichnisnamen in einem bestimmten Verzeichnis durchgehen. Wie kann ich diese Verzeichnisse durchlaufen (und normale Dateien überspringen)?

Beispiel:
Das angegebene Verzeichnis /tmp/
enthält die folgenden Unterverzeichnisse:/tmp/A, /tmp/B, /tmp/C

Ich möchte A, B, C abrufen.

Erik Sapir
quelle

Antworten:

129
cd /tmp
find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n'

Eine kurze Erklärung:

  • find findet Dateien (ganz offensichtlich)

  • .ist das aktuelle Verzeichnis, das nach dem cdis /tmp(IMHO ist dies flexibler als /tmpdirekt im findBefehl. Sie haben nur einen Ort, den Sie cdändern müssen, wenn Sie mehr Aktionen in diesem Ordner ausführen möchten).

  • -maxdepth 1und -mindepth 1stellen Sie sicher, dass findnur im aktuellen Verzeichnis gesucht wird und sich nicht .im Ergebnis befindet

  • -type d sucht nur nach Verzeichnissen

  • -printf '%f\n Gibt für jeden Treffer nur den Namen des gefundenen Ordners (plus eine neue Zeile) aus.

Et voilà!

Boldewyn
quelle
Übrigens: Beachten Sie, dass alle Antworten auch versteckte Ordner finden (dh Ordner, die mit einem Punkt beginnen)! Wenn Sie dies nicht möchten, fügen Sie vor den Optionen printf oder exec `-regex '\ ./ [^ \.]. *'` Hinzu.
Boldewyn
1
Hilfreich - wenn Sie nur einen Befehl für jeden Treffer ausführen müssen ;-) - Nur ich habe mehrere Befehle auszuführen :-(.
Martin
Kein Problem: Passenwhile..done Sie diese Lösung an: transnum.blogspot.de/2008/11/… Innerhalb der Schleife können Sie verrückt werden.
Boldewyn
1
Dies ist eine sehr schöne Methode für einige Anwendungsfälle. findMit der -execOption können Sie einen beliebigen Befehl für jede Datei / jedes Verzeichnis ausführen.
jtpereyda
451

Alle Antworten werden bisher verwendet find, daher hier eine mit nur der Shell. In Ihrem Fall sind keine externen Tools erforderlich:

for dir in /tmp/*/     # list directories in the form "/tmp/dirname/"
do
    dir=${dir%*/}      # remove the trailing "/"
    echo ${dir##*/}    # print everything after the final "/"
done
Ghostdog74
quelle
18
+1 - Warum sich die Mühe machen, findwenn Sie einen Schrägstrich an eine Wildcard anhängen können
Tobias Kienzler
19
Dies for dir in */; do echo $dir; donegilt auch für Verzeichnisse im aktuellen Verzeichnis.
Ehtesh Choudhury
41
Wäre schön wenn es eine Erklärung enthalten könnte was dir=${dir%*/}und was echo ${dir##*/}tut.
Jeremy S.
11
Dies liegt daran, dass die Variable dir am Ende den Backslash enthält. Er entfernt es nur, um einen sauberen Namen zu haben. % entfernt das / am Ende. ## entfernt das / am Anfang. Dies sind Operatoren zum Behandeln von Teilzeichenfolgen.
sfratini
8
Wenn es keine Übereinstimmung gibt, wird die Schleife mit ausgelöst /tmp/*/. Es ist ratsam, eine Überprüfung einzuschließen, um festzustellen, ob das Verzeichnis tatsächlich vorhanden ist.
Jrs42
41

Sie können alle Verzeichnisse einschließlich versteckter Verzeichnisse (beginnend mit einem Punkt) durchlaufen mit:

for file in */ .*/ ; do echo "$file is a directory"; done

Hinweis: Die Verwendung der Liste */ .*/funktioniert in zsh nur, wenn mindestens ein verstecktes Verzeichnis im Ordner vorhanden ist. In Bash wird es auch .und zeigen..


Eine andere Möglichkeit für bash, versteckte Verzeichnisse einzuschließen, wäre die Verwendung von:

shopt -s dotglob;
for file in */ ; do echo "$file is a directory"; done

Wenn Sie Symlinks ausschließen möchten:

for file in */ ; do 
  if [[ -d "$file" && ! -L "$file" ]]; then
    echo "$file is a directory"; 
  fi; 
done

Verwenden Sie diesen in den Schleifen, um nur den nachfolgenden Verzeichnisnamen (A, B, C wie in Frage gestellt) in jeder Lösung auszugeben:

file="${file%/}"     # strip trailing slash
file="${file##*/}"   # strip path and leading slash
echo "$file is the directoryname without slashes"

Beispiel (dies funktioniert auch mit Verzeichnissen, die Leerzeichen enthalten):

mkdir /tmp/A /tmp/B /tmp/C "/tmp/ dir with spaces"
for file in /tmp/*/ ; do file="${file%/}"; echo "${file##*/}"; done
rubo77
quelle
16

Funktioniert mit Verzeichnissen, die Leerzeichen enthalten

Inspiriert von Sorpigal

while IFS= read -d $'\0' -r file ; do 
    echo $file; ls $file ; 
done < <(find /path/to/dir/ -mindepth 1 -maxdepth 1 -type d -print0)

Originaler Beitrag (funktioniert nicht mit Leerzeichen)

Inspiriert von Boldewyn : Beispiel einer Schleife mit findBefehl.

for D in $(find /path/to/dir/ -mindepth 1 -maxdepth 1 -type d) ; do
    echo $D ;
done
zpon
quelle
9
find . -mindepth 1 -maxdepth 1 -type d -printf "%P\n"
Ignacio Vazquez-Abrams
quelle
Das ist auch schön und macht es unnötig basename. Ich würde dies meiner Antwort vorziehen.
Boldewyn
6

Die Technik, die ich am häufigsten benutze, ist find | xargs. Wenn Sie beispielsweise jede Datei in diesem Verzeichnis und alle Unterverzeichnisse weltweit lesbar machen möchten, haben Sie folgende Möglichkeiten:

find . -type f -print0 | xargs -0 chmod go+r
find . -type d -print0 | xargs -0 chmod go+rx

Die -print0Option endet mit einem NULL-Zeichen anstelle eines Leerzeichens. Die -0Option teilt ihre Eingabe auf die gleiche Weise auf. Dies ist also die Kombination für Dateien mit Leerzeichen.

Sie können sich diese Befehlskette so vorstellen, dass sie jede ausgegebene Zeile findam Ende eines chmodBefehls festhält.

Wenn der Befehl, den Sie als Argument ausführen möchten, in der Mitte statt am Ende ausgeführt wird, müssen Sie etwas kreativ sein. Zum Beispiel musste ich in jedes Unterverzeichnis wechseln und den Befehl ausführen latemk -c. Also habe ich (aus Wikipedia ) verwendet:

find . -type d -depth 1 -print0 | \
    xargs -0 sh -c 'for dir; do pushd "$dir" && latexmk -c && popd; done' fnord

Dies hat den Effekt for dir $(subdirs); do stuff; done, ist jedoch für Verzeichnisse mit Leerzeichen in ihren Namen sicher. Außerdem werden die separaten Aufrufe stuffin derselben Shell ausgeführt, weshalb wir in meinem Befehl mit zum aktuellen Verzeichnis zurückkehren müssen popd.

Matthew Leingang
quelle
3

eine minimale Bash-Schleife, aus der Sie aufbauen können (basierend auf der Antwort von ghostdog74)

for dir in directory/*                                                     
do                                                                                 
  echo ${dir}                                                                  
done

um eine ganze Reihe von Dateien nach Verzeichnis zu komprimieren

for dir in directory/*
do
  zip -r ${dir##*/} ${dir}
done                                               
Harry Moreno
quelle
Hier werden alle Dateien aufgelistet directory, nicht nur Unterverzeichnisse.
Dexteritas
2

find . -type d -maxdepth 1

Anonym
quelle
2
Diese Antwort ist gut, außer dass ich die vollständigen Pfade erhalte, während ich nur die Namen der Verzeichnisse möchte.
Erik Sapir
2

Wenn Sie mehrere Befehle in einer for-Schleife ausführen möchten, können Sie das Ergebnis von findwith mapfile(bash> = 4) als Variable speichern und das Array mit durchlaufen ${dirlist[@]}. Es funktioniert auch mit Verzeichnissen, die Leerzeichen enthalten.

Der findBefehl basiert auf der Antwort von Boldewyn. Weitere Informationen zum findBefehl finden Sie dort.

IFS=""
mapfile -t dirlist < <( find . -maxdepth 1 -mindepth 1 -type d -printf '%f\n' )
for dir in ${dirlist[@]}; do
    echo ">${dir}<"
    # more commands can go here ...
done
Dexteritas
quelle