Jak znaleźć pliki w podkatalogach i posortować je według nazwy pliku w jednym poleceniu?

9

Wynik normalnego znalezienia przy użyciu 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

a po sortowaniu według sort -n:

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

jednak pożądanym wynikiem jest:

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

co oznacza, że ​​dane wyjściowe są sortowane tylko na podstawie nazw plików , ale informacje o folderach powinny być przechowywane jako część danych wyjściowych.

Edycja : Spraw, aby przykład był bardziej skomplikowany, ponieważ struktura podkatalogów może obejmować więcej niż jeden poziom.

unode
źródło
2
Zobacz to pytanie, które zadałem na SO: stackoverflow.com/questions/3222810/…
camh
@camh - jeśli to możliwe, chciałbym używać tylko poleceń unix. W każdym razie moje pytanie jest w zasadzie duplikatem twojego. Czy możesz przenieść najlepsze rozwiązanie do tego wątku (i tak zachować link do oryginału), abym mógł zaznaczyć, że to rozwiązanie?
unode
Jeśli @Shawn dokona zmian, które zasugerowałem w moim komentarzu (użyj -printfzamiast awk), myślę, że to najlepsze rozwiązanie. Przerobiłem moją pierwotną implementację, aby używać tej metody.
camh

Odpowiedzi:

9

Musisz posortować według ostatniego pola (traktowanego /jako separator pól). Niestety nie mogę wymyślić narzędzia, które może to zrobić, gdy liczba pól jest różna (jeśli tylko sort -kmoże przyjąć wartości ujemne).

Aby obejść ten problem, musisz wykonać dekorację-sortowanie-undecorate. Oznacza to, że weź nazwę pliku i umieść go na początku, a następnie separator pól, a następnie wykonaj sortowanie, a następnie usuń pierwszą kolumnę i separator pól.

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

To awkpolecenie mówi, że separator pól FS jest ustawiony na /; wpływa to na sposób odczytywania pól. Separatora pole wyświetlania OFS jest również ustawiony /; wpływa to na sposób drukowania rekordów. Następna instrukcja mówi, że wypisz ostatnią kolumnę ( NFto liczba pól w rekordzie, więc jest to również indeks ostatniego pola), a także cały rekord ( $0jest to cały rekord); wydrukuje je z OFS między nimi. Następnie lista jest sortedytowana, traktując ją /jako separator pól - ponieważ w pliku mamy najpierw nazwę pliku, sortuje się według niej. Następnie cutdrukuje tylko pola 2 do końca, ponownie traktując /jako separator pól.

Shawn J. Goff
źródło
3
Ponieważ jest to w przypadku find (1), możesz pominąć część awk i użyć-printf '%f/%p\n'
camh
w rzeczywistości nasza konfiguracja jest nieco bardziej skomplikowana. Zawiera zmienne głębokości podkatalogu. Edytowałem pytanie, aby odzwierciedlić ten fakt. Przepraszam, że na początku tego nie uwzględniłem.
unode
1
@Unode: Rozwiązanie Shawna dobrze radzi sobie ze zmienną głębokością, jest to kanoniczne rozwiązanie tego problemu (do niewielkich odmian).
Gilles „SO- przestań być zły”
4

Używałbym plików „-printf” do wypisania nazwy i ścieżki, sortowania według nazwy i odcięcia nazwy w ostatnim kroku. „###” to tylko marker ułatwiający cięcie.

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

% f drukuje nazwę pliku,% p całą ścieżkę.

Uprościłem polecenie find, aby umieścić je w jednej linii, oczywiście, że opuścisz tę ! -path "./build*"część.

nieznany użytkownik
źródło
3

W zsh ≥4,3.10:

print -l -- **/*.txt~build*(oe\''REPLY=${REPLY:t}'\')
  • **/*.txtdopasowuje rekursywnie*.txt do bieżącego katalogu i jego podkatalogów .
  • ~build* nie obejmuje dopasowań, których tekst zaczyna się od build*(jak ! -path './build*'). (Potrzebujesz setopt extended_globnajpierw.)
  • (oe\''…'\')jest globalnym kwalifikatorem sortującym . REPLY=…konstruuje ciąg do sortowania od ciągu do zwrócenia.
  • ${REPLY:t}to basename („ogon”) ścieżki.
Gilles „SO- przestań być zły”
źródło
Dużo połączonej magii. Ciekawe, ale ograniczamy się do składni sh. +1
unode