x=$(find . -name "*.txt")
echo $x
jeśli uruchomię powyższy fragment kodu w powłoce Bash, otrzymam ciąg zawierający kilka nazw plików oddzielonych spacją, a nie listę.
Oczywiście mogę je dalej oddzielić pustymi, aby uzyskać listę, ale jestem pewien, że jest lepszy sposób, aby to zrobić.
Więc jaki jest najlepszy sposób na przeglądanie wyników find
polecenia?
x=( $(find . -name "*.txt") ); echo "${x[@]}"
Następnie możesz przejść przez pętlęfor item in "${x[@]}"; { echo "$item"; }
Odpowiedzi:
TL; DR: Jeśli jesteś tutaj, aby uzyskać najbardziej poprawną odpowiedź, prawdopodobnie chcesz moich osobistych preferencji
find . -name '*.txt' -exec process {} \;
(patrz na dole tego postu). Jeśli masz czas, przeczytaj resztę, aby zobaczyć kilka różnych sposobów i problemów z większością z nich.Pełna odpowiedź:
Najlepszy sposób zależy od tego, co chcesz zrobić, ale oto kilka opcji. Tak długo, jak żaden plik lub folder w poddrzewie nie ma spacji w nazwie, możesz po prostu zapętlić pliki:
Lepiej marginalnie, wytnij zmienną tymczasową
x
:O wiele lepiej jest globować, kiedy możesz. Bezpieczny spacja dla plików w bieżącym katalogu:
Włączając tę
globstar
opcję, możesz globować wszystkie pasujące pliki w tym katalogu i we wszystkich podkatalogach:W niektórych przypadkach, np. Jeśli nazwy plików są już w pliku, może być konieczne użycie
read
:read
może być bezpiecznie używany w połączeniu zfind
poprzez odpowiednie ustawienie ogranicznika:W przypadku bardziej skomplikowanych wyszukiwań prawdopodobnie będziesz chciał użyć
find
tej-exec
opcji lub z-print0 | xargs -0
:find
może także cd do katalogu każdego pliku przed uruchomieniem polecenia za pomocą-execdir
zamiast-exec
, i może być interaktywny (monit przed uruchomieniem polecenia dla każdego pliku) za pomocą-ok
zamiast-exec
(lub-okdir
zamiast-execdir
).*: Technicznie zarówno
find
ixargs
(domyślnie) uruchomią polecenie z tyloma argumentami, ile mogą zmieścić się w wierszu poleceń, tyle razy, ile potrzeba, aby przejść przez wszystkie pliki. W praktyce, chyba że masz bardzo dużą liczbę plików, nie będzie to miało znaczenia, a jeśli przekroczysz długość, ale potrzebujesz ich wszystkich w tym samym wierszu poleceń,SOLznajdzie inną drogę.źródło
done < filename
i następnym z rurą stdin nie może być stosowany dłużej (→ nie więcej interaktywnej rzeczy wewnątrz pętli), ale w przypadkach, gdy jest to potrzebne można stosować3<
zamiast<
dodać<&3
lub-u3
doread
część, w zasadzie za pomocą oddzielnego deskryptor pliku. Ponadto uważam, żeread -d ''
jest taki sam,read -d $'\0'
ale nie mogę teraz znaleźć żadnej oficjalnej dokumentacji na ten temat.-exec process {} \;
i przypuszczam, że to zupełnie inne pytanie - co to znaczy i jak to manipulować? Gdzie jest dobra Q / A lub doc. na tym?man find
). W takim przypadku-exec
nakazujefind
wykonanie następującego polecenia, zakończonego znakiem;
(lub+
), w którym{}
zostanie zastąpiony nazwą pliku, który przetwarza (lub, jeśli+
jest używany, wszystkich plików, które spełniły ten warunek).-d ''
jest lepszy niż-d $'\0'
. To ostatnie jest nie tylko dłuższe, ale sugeruje również, że możesz przekazać argumenty zawierające bajty puste, ale nie możesz. Pierwszy bajt zerowy oznacza koniec łańcucha. W bash$'a\0bc'
jest taki sam jaka
i$'\0'
jest taki sam jak$'\0abc'
lub tylko pusty ciąg''
.help read
stwierdza, że „ Pierwszy znak delimu służy do zakończenia wprowadzania ”, więc użycie''
jako separatora jest trochę włamaniem. Pierwszym znakiem w pustym łańcuchu jest bajt zerowy, który zawsze oznacza koniec łańcucha (nawet jeśli nie zapisujesz go wprost).Cokolwiek robisz, nie używaj
for
pętli :Trzy powody:
find
należy uruchomić do końca.for
pętla zwraca 40 KB tekstu. Ostatnie 8 KB zostanie zrzucone zfor
pętli i nigdy się tego nie dowiesz.Zawsze używaj
while read
konstrukcji:Pętla zostanie wykonana podczas wykonywania
find
polecenia. Ponadto to polecenie będzie działać, nawet jeśli nazwa pliku zostanie zwrócona z białymi spacjami. I nie przepełnisz bufora wiersza poleceń.-print0
Użyje NULL jako separator pliku zamiast nowej linii i-d $'\0'
użyje NULL jako separator podczas czytania.źródło
-exec
Zamiast tego użyj find's .-exec
jest najbezpieczniejszy, ponieważ w ogóle nie używa powłoki. Jednak NL w nazwach plików jest dość rzadkie. Spacje w nazwach plików są dość powszechne. Najważniejsze jest, aby nie używaćfor
pętli zalecanej przez wiele plakatów.for file $(find)
powodu problemów z tym związanych.-r
opcji, abyread
:-r raw input - disables interpretion of backslash escapes and line-continuation in the read data
Uwaga: tej metody i (drugiej) metody przedstawionej przez bmargulies można bezpiecznie używać z białymi spacjami w nazwach plików / folderów.
Aby uwzględnić także - nieco egzotyczny - przypadek znaków nowej linii w nazwach plików / folderów, będziesz musiał skorzystać z
-exec
predykatufind
takiego:{}
Jest zastępczy dla elementu znajdując i\;
służy do zakończenia-exec
orzecznik.A dla kompletności dodam jeszcze jeden wariant - musisz pokochać * nix sposoby ich wszechstronności:
To by oddzieliło drukowane elementy
\0
znakiem, który nie jest dozwolony w żadnym systemie plików w nazwach plików lub folderów, o ile wiem, i dlatego powinien obejmować wszystkie podstawy.xargs
zbiera je kolejno jeden po drugim ...źródło
find -print0
ixargs -0
są to zarówno argumenty rozszerzenia GNU, jak i nieprzenośne (POSIX) argumenty. Niezwykle przydatne w systemach, które je mają!read -r
mogłyby naprawić) lub nazw plików kończących się białymi spacjami (któreIFS= read
mogłyby naprawić). Stąd BashFAQ # 1 sugerujewhile IFS= read -r filename; do ...
exit
nie będzie działać zgodnie z oczekiwaniami, a zmienne ustawione w ciele pętli nie będą dostępne po pętli.Nazwy plików mogą zawierać spacje, a nawet znaki kontrolne. Spacje są (domyślnymi) ogranicznikami rozszerzania powłoki w bash i dlatego
x=$(find . -name "*.txt")
nie są w ogóle zalecane. Jeśli find otrzyma nazwę pliku ze spacjami, np."the file.txt"
Otrzymasz 2 oddzielne ciągi do przetworzenia, jeśli przetworzyszx
w pętli. Możesz to poprawić, zmieniając separator (IFS
zmienna bash ) np. Na\r\n
, ale nazwy plików mogą zawierać znaki kontrolne - więc nie jest to (całkowicie) bezpieczna metoda.Z mojego punktu widzenia istnieją 2 zalecane (i bezpieczne) wzorce przetwarzania plików:
1. Użyj do rozwijania pętli i nazw plików:
2. Użyj funkcji znajdź odczyt podczas odczytu i podstawienia procesu
Uwagi
na wzorze 1:
nullglob
Można użyć opcji powłoki, aby uniknąć tej dodatkowej linii.failglob
opcję powłoki i nie znaleziono żadnych dopasowań, drukowany jest komunikat o błędzie i polecenie nie jest wykonywane.” (z podręcznika Bash powyżej)globstar
: „Jeśli jest ustawiony, wzorzec„ ** ”użyty w kontekście rozszerzenia nazwy pliku będzie pasował do wszystkich plików i zero lub więcej katalogów i podkatalogów. Jeśli po wzorcu występuje„ / ”, pasują tylko katalogi i podkatalogi.” patrz Podręcznik Bash, wbudowany Shoptextglob
,nocaseglob
,dotglob
i zmienna powłokiGLOBIGNORE
na wzorze 2:
nazwy plików mogą zawierać spacje, tabulatory, spacje, znaki nowej linii, ... w celu bezpiecznego przetwarzania nazw plików w bezpieczny sposób,
find
przy-print0
użyciu: nazwa pliku jest drukowana ze wszystkimi znakami kontrolnymi i kończy się na NUL. zobacz także Gnu Findutils Manpage, Niebezpieczna obsługa nazw plików , bezpieczna obsługa nazw plików , nietypowe znaki w nazwach plików . David A. Wheeler poniżej zawiera szczegółowe omówienie tego tematu.Istnieje kilka możliwych wzorców przetwarzania wyników wyszukiwania w pętli while. Inni (kevin, David W.) pokazali, jak to zrobić za pomocą rur:
Gdy wypróbujesz ten fragment kodu, zobaczysz, że nie działa:files_found
jest zawsze „prawdziwy”, a kod zawsze będzie powtarzał „nie znaleziono plików”. Powód jest taki: każde polecenie potoku jest wykonywane w osobnej podpowłoce, więc zmienna zmienna w pętli (osobna podpowłoka) nie zmienia zmiennej w głównym skrypcie powłoki. Dlatego zalecam stosowanie podstawienia procesu jako „lepszego”, bardziej użytecznego, bardziej ogólnego wzorca.Zobacz : Ustawiam zmienne w pętli, która jest w potoku. Dlaczego znikają ... (z FAQ Grega Basha) w celu szczegółowej dyskusji na ten temat.
Dodatkowe referencje i źródła:
Podręcznik Gnu Bash, dopasowywanie wzorów
Nazwy plików i ścieżki w powłoce: Jak to zrobić poprawnie, David A. Wheeler
Dlaczego nie czytasz wierszy z „for”, Wiki Grega
Dlaczego nie powinieneś analizować wyników ls (1), Wiki Grega
Podręcznik Gnu Bash, zastępowanie procesów
źródło
(Zaktualizowano w celu uwzględnienia wyjątkowej poprawy prędkości @ Socowi)
Z każdym,
$SHELL
który obsługuje (dash / zsh / bash ...):Gotowe.
Oryginalna odpowiedź (krótsza, ale wolniejsza):
źródło
\;
możesz użyć,+
aby przekazać jak najwięcej plików do jednegoexec
. Następnie użyj"$@"
wewnątrz skryptu powłoki, aby przetworzyć wszystkie te parametry.$@
pomija go, ponieważ zwykle jest to nazwa skryptu. Po prostu trzeba dodaćdummy
w między'
i{}
tak można ją zastąpić nazwą skryptu, zapewniając wszystkie mecze są przetwarzane przez pętlę.OTHERVAR=foo find . -na.....
powinien umożliwiać dostęp$OTHERVAR
z poziomu nowo utworzonej powłoki.źródło
for x in $(find ...)
zepsuje się dla każdej nazwy pliku z białymi spacjami. To samo dotyczy,find ... | xargs
chyba że użyjesz-print0
i-0
find . -name "*.txt -exec process_one {} ";"
zamiast tego. Dlaczego powinniśmy używać xargs do zbierania wyników, które już mamy?process_one
jest. Jeśli jest to symbol zastępczy rzeczywistego polecenia , upewnij się, że zadziała (jeśli poprawisz literówkę i dodasz cudzysłowy zamykające po"*.txt
). Ale jeśliprocess_one
funkcja jest zdefiniowana przez użytkownika, kod nie będzie działał.Możesz zapisać swoje
find
dane wyjściowe w tablicy, jeśli chcesz później je wykorzystać jako:Teraz, aby wydrukować każdy element w nowym wierszu, możesz albo użyć
for
iteracji pętli do wszystkich elementów tablicy, albo możesz użyć instrukcji printf.lub
Możesz także użyć:
Spowoduje to wydrukowanie każdej nazwy pliku w nowym wierszu
Aby wydrukować dane
find
wyjściowe tylko w formie listy, możesz użyć jednej z następujących czynności:lub
Spowoduje to usunięcie komunikatów o błędach i podanie nazwy pliku tylko w nowym wierszu.
Jeśli chcesz zrobić coś z nazwami plików, dobrze jest przechowywać je w tablicy, w przeciwnym razie nie ma potrzeby zajmowania tego miejsca i możesz bezpośrednio wydrukować dane wyjściowe
find
.źródło
Jeśli możesz założyć, że nazwy plików nie zawierają znaków nowej linii, możesz odczytać dane wyjściowe
find
do tablicy Bash za pomocą następującego polecenia:Uwaga:
-t
powodujereadarray
usunięcie nowych linii.readarray
jest w rurze, stąd podstawienie procesu.readarray
jest dostępny od wersji Bash 4.Bash 4.4 i nowsze wersje obsługują również
-d
parametr określający ogranicznik. Użycie znaku null zamiast znaku nowej linii do określenia nazw plików działa również w rzadkich przypadkach, gdy nazwy plików zawierają znaki nowej linii:readarray
można również wywołać, jak wmapfile
przypadku tych samych opcji.Odniesienie: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
źródło
exit
podczas zapętlania wynikówreadarray -d '' x < <(find . -name '*.txt' -print0)
Lubię używać find, który jest najpierw przypisany do zmiennej, a IFS przełącza się na nową linię w następujący sposób:
Na wypadek gdybyś chciał powtórzyć więcej akcji na tym samym zestawie DANYCH i znaleźć na swoim serwerze bardzo wolno (wysokie wykorzystanie I / 0)
źródło
Możesz umieścić zwrócone nazwy plików
find
w tablicy takiej jak ta:Teraz możesz po prostu przejść przez tablicę, aby uzyskać dostęp do poszczególnych elementów i robić z nimi, co chcesz.
Uwaga: jest bezpieczny dla białej przestrzeni.
źródło
mapfile -t -d '' array < <(find ...)
. UstawienieIFS
nie jest koniecznemapfile
.na podstawie innych odpowiedzi i komentarza @phk, używając fd # 3:
(co wciąż pozwala na użycie stdin wewnątrz pętli)
źródło
find <path> -xdev -type f -name *.txt -exec ls -l {} \;
Spowoduje to wyświetlenie listy plików i podanie szczegółowych informacji o atrybutach.
źródło
A może użyjesz grep zamiast find?
Teraz możesz przeczytać ten plik, a nazwy plików mają postać listy.
źródło