Wie finde ich Dateien in Unterverzeichnissen und sortiere sie in einem einzigen Befehl nach Dateinamen?

9

Ergebnis eines normalen Funds mit find . ! -path "./build*" -name "*.txt":

./tool/001-sub.txt
./tool/000-main.txt
./zo/001-int.txt
./zo/id/002-and.txt
./as/002-mod.txt

und wenn sortiert mit sort -n:

./as/002-mod.txt
./tool/000-main.txt
./tool/001-sub.txt
./zo/001-int.txt
./zo/id/002-and.txt

Die gewünschte Ausgabe ist jedoch:

./tool/000-main.txt
./zo/001-int.txt
./tool/001-sub.txt
./zo/id/002-and.txt
./as/002-mod.txt

Dies bedeutet, dass die Ausgabe nur nach dem Dateinamen sortiert wird, die Ordnerinformationen jedoch als Teil der Ausgabe beibehalten werden sollten.

Bearbeiten : Machen Sie das Beispiel komplizierter, da die Unterverzeichnisstruktur mehr als eine Ebene enthalten kann.

unode
quelle
2
Siehe diese Frage, die ich auf SO gestellt habe: stackoverflow.com/questions/3222810/…
camh
@camh - wenn möglich möchte ich nur Unix-Befehle verwenden. Auf jeden Fall ist meine Frage so ziemlich ein Duplikat von Ihnen. Können Sie die beste Lösung auf diesen Thread übertragen (behalten Sie trotzdem einen Link zum Original), damit ich sie als Lösung markieren kann?
Unode
Wenn @Shawn die Änderungen vornimmt, die ich in meinem Kommentar vorgeschlagen habe ( -printfanstelle von verwenden awk), halte ich dies für die beste Lösung. Ich habe meine ursprüngliche Implementierung überarbeitet, um diese Methode zu verwenden.
Camh

Antworten:

9

Sie müssen nach dem letzten Feld sortieren (als /Feldtrennzeichen betrachtet). Leider kann ich mir kein Tool vorstellen, das dies kann, wenn die Anzahl der Felder variiert (wenn nur sort -knegative Werte angenommen werden könnten).

Um dies zu umgehen, müssen Sie ein Dekorieren-Sortieren-Undekorieren durchführen. Nehmen Sie den Dateinamen und setzen Sie ihn an den Anfang, gefolgt von einem Feldtrennzeichen. Führen Sie dann eine Sortierung durch und entfernen Sie die erste Spalte und das Feldtrennzeichen.

find . ! -path "./build*" -name "*.txt" |\
    awk -vFS=/ -vOFS=/ '{ print $NF,$0 }' |\
    sort -n -t / |\
    cut -f2- -d/

Dieser awkBefehl besagt, dass das Feldtrennzeichen auf gesetzt FS ist /. Dies wirkt sich auf die Art und Weise aus, wie Felder gelesen werden. Das Ausgabefeldtrennzeichen OFS ist ebenfalls auf eingestellt /. Dies wirkt sich auf die Art und Weise aus, wie Datensätze gedruckt werden. Die nächste Anweisung besagt, dass die letzte Spalte ( NFdie Anzahl der Felder im Datensatz, also zufällig auch der Index des letzten Felds) sowie der gesamte Datensatz ( $0der gesamte Datensatz) gedruckt werden sollen. es druckt sie mit dem OFS zwischen ihnen. Dann wird die Liste sortbearbeitet und /als Feldtrennzeichen behandelt. Da wir den Dateinamen zuerst im Datensatz haben, wird er danach sortiert. Dann cutdruckt /das Feld nur die Felder 2 bis zum Ende aus und wird erneut als Feldtrennzeichen behandelt.

Shawn J. Goff
quelle
3
Da dies mit find (1) ist, können Sie den awk-Teil überspringen und-printf '%f/%p\n'
camh
in der Tat ist unser Setup etwas komplizierter. Es enthält variable Subdir-Tiefen. Die Frage wurde bearbeitet, um diese Tatsache widerzuspiegeln. Ich entschuldige mich dafür, dass ich dies zunächst nicht aufgenommen habe.
Unode
1
@Unode: Shawns Lösung verarbeitet variable Tiefen einwandfrei. Dies ist die kanonische Lösung für dieses Problem (bis zu geringfügigen Abweichungen).
Gilles 'SO - hör auf böse zu sein'
4

Ich würde Dateien '-printf' verwenden, um Name und Pfad auszugeben, nach Namen zu sortieren und den Namen in einem letzten Schritt abzuschneiden. '###' ist nur ein Marker, um das Schneiden zu erleichtern.

find -name "*.txt" -printf "%f###%p\n" | sort -n | sed 's/.*###//'

% f gibt den Dateinamen aus,% p den gesamten Pfad.

Ich habe den Befehl find vereinfacht, um ihn in eine Zeile zu bringen. Natürlich würden Sie das ! -path "./build*"Teil verlassen .

Benutzer unbekannt
quelle
3

In zsh ≥4.3.10:

print -l -- **/*.txt~build*(oe\''REPLY=${REPLY:t}'\')
  • **/*.txtÜbereinstimmungen *.txtim aktuellen Verzeichnis und seinen Unterverzeichnissen rekursiv .
  • ~build* schließt Übereinstimmungen aus, deren Text mit build*(like ! -path './build*') beginnt . (Du brauchst setopt extended_globzuerst.)
  • (oe\''…'\')ist ein Sorting Glob Qualifier . REPLY=…Konstruiert die zu sortierende Zeichenfolge aus der zurückzugebenden Zeichenfolge.
  • ${REPLY:t}ist der Basisname ("Schwanz") des Pfades.
Gilles 'SO - hör auf böse zu sein'
quelle
Viel verkettete Magie. Interessant, aber wir beschränken uns auf die sh-Syntax. +1
Unode