Wie kann ich nur Verzeichnisse in Bash durchlaufen?

249

Ich habe einen Ordner mit einigen Verzeichnissen und einigen Dateien (einige sind versteckt, beginnend mit Punkt).

for d in *; do
 echo $d
done

Alle Dateien werden durchlaufen, aber ich möchte nur Verzeichnisse durchlaufen. Wie mache ich das?

rubo77
quelle

Antworten:

332

Sie können einen Schrägstrich am Ende angeben, um nur Verzeichnisse zu finden:

for d in */ ; do
    echo "$d"
done
Choroba
quelle
7
Beachten Sie, dass es auch Symlinks zu Verzeichnissen enthält.
Stéphane Chazelas
1
Wie würden Sie dann Symlinks ausschließen?
Rubo77
3
@ rubo77: Du kannst testen, [[ -L $d ]]ob $ d ein symbolischer Link ist.
Choroba
1
@AsymLabs Das ist falsch, set -Pbetrifft nur Befehle, die das Verzeichnis wechseln. sprunge.us/TNac
Chris Down
4
@choroba: [[ -L "$f" ]]Symlinks werden in diesem Fall nicht mit ausgeschlossen */, Sie müssen den abschließenden Schrägstrich mit [[ -L "${f%/}" ]]
entfernen
78

Sie können testen mit -d:

for f in *; do
    if [ -d "$f" ]; then
        # $f is a directory
    fi
done

Dies ist einer der Dateitestoperatoren .

Goldlöckchen
quelle
8
Beachten Sie, dass Symlinks zu Verzeichnissen enthalten sind.
Stéphane Chazelas
21
if [[ -d "$f" && ! -L "$f" ]]schließt
symlinks
Bricht bei leerem Verzeichnis ab. if [[ "$f" = "*" ]]; then continue; fi
Piskvor
30

Beachten Sie, dass die elegante Lösung von choroba unerwartetes Verhalten hervorrufen kann, wenn im aktuellen Verzeichnis keine Verzeichnisse verfügbar sind. In diesem Zustand wird die forSchleife nicht übersprungen, sondern genau einmal ausgeführt, wobei dgilt */:

#!/usr/bin/env bash

for d in */; do
    # Will print */ if no directories are available
    echo $d
done

Ich empfehle Folgendes, um sich vor diesem Fall zu schützen:

#!/usr/bin/env bash

for f in *; do
    if [ -d ${f} ]; then
        # Will not run if no directories are available
        echo $f
    fi
done

Dieser Code durchläuft alle Dateien im aktuellen Verzeichnis, überprüft, ob fes sich um ein Verzeichnis handelt, und gibt ein Echo aus, fwenn die Bedingung wahr ist. Wenn fgleich */, echo $fwird nicht ausgeführt.

emagdne
quelle
3
Viel einfacher zu shopt -s nullglob.
Choroba
21

Wenn Sie spezifischere Dateien als nur Verzeichnisse auswählen müssen, verwenden Sie diese findund übergeben Sie sie an while read:

shopt -s dotglob
find * -prune -type d | while IFS= read -r d; do 
    echo "$d"
done

Verwenden Sie shopt -u dotglobdiese Option, um versteckte Verzeichnisse (oder setopt dotglob/ unsetopt dotglobin zsh) auszuschließen .

IFS=Um zu vermeiden, dass Dateinamen aufgeteilt werden, die eine der folgenden Angaben enthalten $IFS:'a b'

siehe AsymLabs beantworten unten für weitere findOptionen


edit:
Falls Sie einen Exit-Wert innerhalb der while-Schleife erstellen müssen, können Sie die zusätzliche Subshell mit folgendem Trick umgehen:

while IFS= read -r d; do 
    if [ "$d" == "something" ]; then exit 1; fi
done < <(find * -prune -type d)
rubo77
quelle
2
Ich fand diese Lösung auf: stackoverflow.com/a/8489394/1069083
rubo77
11

Sie können dafür reines bash verwenden, aber es ist besser, find zu verwenden:

find . -maxdepth 1 -type d -exec echo {} \;

(find schließt zusätzlich versteckte Verzeichnisse ein)

eilen
quelle
8
Beachten Sie, dass Symlinks zu Verzeichnissen nicht enthalten sind. Sie können shopt -s dotglobfor verwenden bash, um versteckte Verzeichnisse einzuschließen. Ihr wird auch enthalten .. Beachten Sie auch, dass dies -maxdepthkeine Standardoption -pruneist.
Stéphane Chazelas
2
Die dotglobOption ist interessant, dotglobgilt aber nur für die Verwendung von *. find .wird immer versteckte Verzeichnisse (und das aktuelle Verzeichnis auch) enthalten
rubo77
6

Dies geschieht, um sowohl sichtbare als auch versteckte Verzeichnisse im aktuellen Arbeitsverzeichnis zu finden, mit Ausnahme des Stammverzeichnisses:

So durchlaufen Sie Verzeichnisse:

 find -path './*' -prune -type d

Symlinks in das Ergebnis aufnehmen:

find -L -path './*' -prune -type d

Um etwas mit jedem Verzeichnis zu tun (außer Symlinks):

find -path './*' -prune -type d -print0 | xargs -0 <cmds>

versteckte Verzeichnisse ausschließen:

find -path './[^.]*' -prune -type d

So führen Sie mehrere Befehle für die zurückgegebenen Werte aus (ein sehr ausgeklügeltes Beispiel):

find -path './[^.]*' -prune -type d -print0 | xargs -0 -I '{}' sh -c \
"printf 'first: %-40s' '{}'; printf 'second: %s\n' '{}'"

Anstelle von 'sh -c' kann auch 'bash -c' usw. verwendet werden.

AsymLabs
quelle
Was passiert, wenn das Echo '* /' in 'für d in echo * /' beispielsweise 60.000 Verzeichnisse enthält?
AsymLabs
Sie werden den Fehler "Zu viele Dateien" erhalten, der jedoch behoben werden kann: So umgehen Sie "Zu viele offene Dateien" in debian
rubo77
1
Das xargs-Beispiel funktioniert nur für eine Teilmenge der Verzeichnisnamen 9e.g. diejenigen mit Leerzeichen oder Zeilenumbrüchen würden nicht funktionieren). Besser zu verwenden, ... -print0 | xargs -0 ...wenn Sie die genauen Namen nicht kennen.
Anthon
1
@ rubo77 hinzugefügt, um versteckte Dateien / Verzeichnisse und die Ausführung mehrerer Befehle auszuschließen - dies kann natürlich auch mit einem Skript erfolgen.
AsymLabs
1
Ich habe in meiner Antwort unten ein Beispiel hinzugefügt, wie man mit einer Funktion mitfind * | while read file; do ...
rubo77 22.10.13
3

Sie können alle Verzeichnisse einschließlich versteckter Verzeichnisse (beginnend mit einem Punkt) in einer Zeile und mit mehreren Befehlen durchlaufen:

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

Hinweis: die Liste mit */ .*/Arbeiten in der Bash, sondern zeigt auch die Ordner .und ..während in zsh wird es nicht diese zeigt aber einen Fehler aus , wenn es keine versteckte Datei in dem Ordner ist

Eine sauberere Version, die versteckte Verzeichnisse enthält und ../ ausschließt, enthält die Option dotglob:

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

(oder setopt dotglobin zsh)

Sie können dotglob mit deaktivieren

shopt -u dotglob
rubo77
quelle
2

Dies wird den vollständigen Pfad in jedem Verzeichnis in der Liste enthalten:

for i in $(find $PWD -maxdepth 1 -type d); do echo $i; done
rubo77
quelle
1
Pausen bei der Verwendung von Ordnern mit Leerzeichen
Alejandro Sazo
0

Verwenden Sie findmit -exec, um die Verzeichnisse zu durchlaufen und eine Funktion in der Option exec aufzurufen :

dosomething () {
  echo "doing something with $1"
}
export -f dosomething
find -path './*' -prune -type d -exec bash -c 'dosomething "$0"' {} \;

Verwenden Sie shopt -s dotgloboder shopt -u dotglob, um versteckte Verzeichnisse ein- oder auszuschließen

rubo77
quelle
0

Dies listet alle Verzeichnisse zusammen mit der Anzahl der Unterverzeichnisse in einem bestimmten Pfad auf:

for directory in */ ; do D=$(readlink -f "$directory") ; echo $D = $(find "$D" -mindepth 1 -type d | wc -l) ; done
pabloa98
quelle
-1
ls -d */ | while read d
do
        echo $d
done
dcat
quelle
This is one directory with spaces in the name- aber wird als mehrere analysiert.
Piskvor
-5
ls -l | grep ^d

oder:

ll | grep ^d

Sie können es als Alias ​​festlegen

user250676
quelle
1
Leider glaube ich nicht, dass dies die Frage beantwortet, die lautete: "Ich möchte nur Verzeichnisse durchlaufen" - und die sich geringfügig von dieser lsAntwort unterscheidet, in der Verzeichnisse aufgelistet sind .
Jeff Schaller
1
Es ist nie eine gute Idee , die Ausgabe zu analysieren ls: mywiki.wooledge.org/ParsingLs
codeforester