Linux finden-Typ d: Wie listet man die tiefsten Verzeichnisse auf, z. B. ./dir1/dir2 ohne ./dir1?

Linux finden-Typ d: Wie listet man die tiefsten Verzeichnisse auf, z. B. ./dir1/dir2 ohne ./dir1?
find . -type d

normalerweise wird angezeigt

./dir1
./dir1/dir2
./dir3
./dir3/dir4
./dir3/dir4/dir5
...

Ich möchte nur die

./dir1/dir2
./dir3/dir4/dir5

, ohne sein übergeordnetes ./dir1, …

Mit anderen Worten, ich möchte nur Verzeichnisse sehen, die keine Unterverzeichnisse enthalten.

Irgendeine Idee?

EDIT: Ich habe festgestellt, dass es -links 2in einer normalen Linux-Umgebung funktioniert, z. B.

docker run -it --rm ubuntu:bionic find /etc -type d -links 2

funktioniert perfekt.

Wenn ich jedoch ein Verzeichnis (von MacOS oder Windows) in den Docker-Container einbinde, ändern sich die Dinge und es funktioniert nicht. Sie können Folgendes versuchen:

docker run -it --rm -v /etc:/etc_of_host ubuntu:bionic find /etc_of_host -type d -links 2

Antwort1

Mit zsh:

has_subdirs() () (( $# )) ${1-$REPLY}/*(ND/Y1)
print -rC1 -- **/*(ND/^+has_subdirs)

Oder direkt ohne Vermittlerfunktion:

print -rC1 -- **/*(ND/^e['()(($#)) $REPLY/*(ND/Y1)'])

Das ist:

  • fn() some-commanddefiniert eine fnFunktion mit some-commandals Hauptteil wie in der Bourne-Shell und den meisten Bourne-ähnlichen Shells.
  • () { code } argseine anonyme Funktion, die codemit argsals Positionsparameter ausgeführt wird.
  • (( $# ))hier ist der Körper dieser anonymen Funktion ein arithmetischer Ausdruck, der sich auflöst zuWAHRwenn $#(die Anzahl der Argumente) ungleich Null ist.
  • ${param-default}(aus der Bourne-Shell) wird zu erweitert, $paramwenn der Parameter gesetzt ist oder defaultandernfalls. Hier mit ${1-$REPLY}ermöglichen wir den direkten Aufruf unserer Funktion (als has_subdirs mydir) oder als Globbing-Qualifizierungsfunktion wie unten.
  • $dir/*(ND/Y1)erweitert sich auf die Dateien vom Typ „Verzeichnis in“ $dir( /), einschließlich der versteckten ( Dotglob), und gibt keinen Fehler aus, wenn es keine Übereinstimmung gibt ( Nullglob). Aber mit Y1stoppen wir bei der ersten Übereinstimmung. Daher gibt die anonyme Funktion (und daher has_subdirs) „true“ zurück, wenn das Verzeichnis mindestens ein Unterverzeichnis enthält.
  • print -rC1druckt seine Argumente raw auf 1 CSpalte
  • ^+has_subdirsbeschränkt auf Dateien, für die die has_subdirsFunktion () nicht ^true zurückgibt.

Wenn Sie die Shell verwenden müssen bash, tun Sie einfach Folgendes:

zsh << 'EOF'
  print -rC1 -- **/*(ND/^e['()(($#)) $REPLY/*(ND/Y1)'])
EOF

Oder um das Ergebnis in einem Bash-Array zu speichern (erfordert Bash 4.4+):

readarray -td '' array < <(zsh << 'EOF'
  print -rNC1 -- **/*(ND/^e['()(($#)) $REPLY/*(ND/Y1)'])
EOF
)

(unter Verwendung von NUL-getrennten Datensätzen, um beliebige Pfade speichern zu können).

Wenn Sie nicht über zsh, aber über verfügen perl, können Sie Folgendes tun:

find . -depth -type d -print0 |
  perl -l -0ne 'print if rindex $prev, "$_/", 0; $prev = $_'

Oder wenn Sie GNU haben awk:

find . -depth -type d -print0 |
  gawk -v 'RS=\0' 'index(prev, $0"/") != 1; {prev = $0}'

Diese haben finddie Verzeichnisse gedrucktTiefe zuerst(Blätter vor den Zweigen, auf denen sie sich befinden) und ruft perl/ ab awk, um die Datensätze auszudrucken, die nicht gefunden wurden, gefolgt von /am Anfang des vorherigen Datensatzes.

Um die Dateien in einem Bash 4.4+-Array zu speichern, müssen Sie in der Ausgabe auf NUL-getrennte Datensätze umschalten, indem Sie das -lNach -0-in- perloder Add- -v 'ORS=\0'in-Element verschieben gawk:

readarray -td array < <(
  find . -depth -type d -print0 |
    perl -0lne 'print if rindex $prev, "$_/", 0; $prev = $_'
)
readarray -td array < <(
  find . -depth -type d -print0 |
    gawk -v 'RS=\0' -v 'ORS=\0' 'index(prev, $0"/") != 1; {prev = $0}'
)

Auf einigen Systemen und Dateisystemen (wie etwa ext4Dateisystemen auf Linux-basierten Systemen) können Sie sich möglicherweise darauf verlassen, dass Verzeichnisse mit Unterverzeichnissen eine Linkanzahl von mehr als 2 haben ( dirund dir/.für jedes Unterverzeichnis ein zusätzlicher Link dir/subdir/..).

print -rC1 -- **/*(ND/l-3)

Oder verwenden Sie findin einer beliebigen Shell:

find . -type d -links -3

Dies funktioniert beispielsweise nicht auf btrfs(das in mehreren Linux-Distributionen das Standarddateisystem ersetzt hat ext4), wo die Linkanzahl der Verzeichnisse immer eins beträgt. Daher würde ich dies nicht als allgemeine Lösung empfehlen.

Dasselbe gilt für Union-Dateisysteme, wie in Ihrem Fall, wie overlayfsdort, wo ich einige findezusammengeführtVerzeichnisse haben eine Linkanzahl von 1, unabhängig davon, ob sie Unterverzeichnisse enthalten oder nicht.

Wo immer es jedoch verwendbar ist, hat es gegenüber Lösungen, die die Unterverzeichnisse manuell zählen, den Vorteil, dass Sie keinen Lesezugriff auf das Verzeichnis benötigen (nur Suchzugriff auf das übergeordnete Verzeichnis).

Antwort2

Was Sie suchen, ist:

find . -type d -links 2

Jedes Verzeichnis im Dateisystem hat mindestens zwei Hardlinks – das Verzeichnis selbst im übergeordneten Verzeichnis und den Eintrag „.“ Jeder Eintrag „..“ in den Unterverzeichnissen fügt einen neuen Hardlink zum Verzeichnis hinzu. Sie müssen also nur Verzeichnisse mit zwei Hardlinks finden.

Der in einer anderen Antwort vorgeschlagene Befehl

find . -type d -links -3

Tut dasselbe, aber mit der Anweisung „Verzeichnisse mit weniger als drei Hardlinks dazu.“

Antwort3

Die Sortiertesten:

p="";(find . -type d;echo) | while read d; do [[ $p && $d != $p/* ]] && echo $p; p=$d; done

In einer Zeile

a="";(find . -type d;echo )|while read i; do if  [[ (! $i =~ $a) && ("$a" != "") ]]; then echo $a; fi; a=$i;done

Wie im OP erwähnt:

a="";(find . -type d;echo )|while read i; do [[ $i != $a/* && ! -z "$a" ]] && echo $a; a=$i;done

Antwort4

Lesen man find. Die -mindepthOption ist die gewünschte.

verwandte Informationen