Ciągle sprawdzam składnię
find . -name "FILENAME" -exec rm {} \;
głównie dlatego, że nie rozumiem, jak dokładnie działa część -exec
. Jakie jest znaczenie nawiasów klamrowych, ukośnika odwrotnego i średnika? Czy są inne przypadki użycia tej składni?
Komentarze
Odpowiedź
Ta odpowiedź jest podzielona na następujące części:
- Podstawowe użycie
-exec
- Używanie
-exec
w połączeniu zsh -c
- Używanie
-exec ... {} +
- Używanie
-execdir
Podstawowe użycie -exec
Opcja -exec
przyjmuje zewnętrzne narzędzie z opcjonalnymi argumentami jako jego argument i wykonuje go.
Jeśli ciąg {}
znajduje się w dowolnym miejscu w danym poleceniu, każde jego wystąpienie zostanie zastąpione aktualnie przetwarzaną nazwą ścieżki ( np. ./some/path/FILENAME
). W większości powłok dwa znaki {}
nie muszą być cytowane.
Polecenie musi być zakończone ;
dla find
, aby wiedzieć, gdzie się kończy (ponieważ później mogą być dalsze opcje s). Aby chronić ;
przed powłoką, należy go cytować jako \;
lub ";"
, w przeciwnym razie powłoka zobaczy to jako koniec polecenia find
.
Przykład (\
w koniec pierwszych dwóch wierszy służy tylko do kontynuacji wiersza):
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} ";"
Spowoduje to znalezienie wszystkich zwykłych plików (-type f
) których nazwy pasują do wzorca *.txt
w bieżącym katalogu lub poniżej. Następnie sprawdzi, czy ciąg hello
występuje w którymkolwiek ze znalezionych plików przy użyciu grep -q
(co nie daje żadnego wyniku, tylko wyjście status). W przypadku plików zawierających ciąg znaków cat
zostanie wykonany w celu wyświetlenia zawartości pliku na terminalu.
Każdy -exec
działa również jak „test” na ścieżkach znalezionych przez find
, podobnie jak -type
i -name
tak. Jeśli polecenie zwróci zerowy kod zakończenia (oznaczający „powodzenie”), rozważana jest następna część polecenia find
, w przeciwnym razie find
Polecenie jest kontynuowane z następną nazwą ścieżki. Jest to używane w powyższym przykładzie do znalezienia plików zawierających ciąg hello
, ale do zignorowania wszystkich innych plików.
Powyższy przykład ilustruje dwa najpopularniejsze zastosowania przypadki -exec
:
- Jako test w celu dalszego ograniczenia wyszukiwania.
- Aby wykonać jakąś akcję na znalezionym nazwa ścieżki (zwykle, ale niekoniecznie, na końcu polecenia
find
).
Używanie -exec
w połączeniu z sh -c
Polecenie, które -exec
może wykonać, jest ograniczone do zewnętrznego narzędzia z opcjonalnymi argumentami.Używanie wbudowanych elementów powłoki, funkcji, warunków, potoków, przekierowań itp. Bezpośrednio z -exec
nie jest możliwe, chyba że jest opakowane w coś takiego jak sh -c
child shell.
Jeśli bash
funkcje są wymagane, użyj bash -c
zamiast sh -c
.
sh -c
uruchamia /bin/sh
ze skryptem podanym w wierszu poleceń, po którym następują opcjonalne argumenty wiersza poleceń do tego skryptu.
Prosty przykład użycia samego sh -c
, bez find
:
sh -c "echo "You gave me $1, thanks!"" sh "apples"
Przekazuje dwa argumenty do potomnego skryptu powłoki. Zostaną one umieszczone w $0
i $1
, aby skrypt mógł użyć.
-
string
sh
. Będzie on dostępny jako$0
wewnątrz skryptu, a jeśli wewnętrzna powłoka wyświetli komunikat o błędzie, będzie poprzedzać go tym ciągiem. -
Argument
apples
jest dostępny jako$1
w skrypcie i gdyby było więcej argumentów, byłyby one dostępne jako$2
,$3
itd. Będą również dostępne na liście"$@"
(z wyjątkiem$0
, który nie byłby częścią"$@"
).
Jest to przydatne w połączeniu z -exec
, ponieważ pozwala nam tworzyć dowolnie złożone skrypty działające na ścieżkach znalezionych przez find
.
Przykład: Znajdź wszystkie zwykłe pliki, które mają określony przyrostek nazwy pliku i zmień ten przyrostek na inny, gdzie przyrostki są przechowywane w zmiennych:
from=text # Find files that have names like something.text to=txt # Change the .text suffix to .txt find . -type f -name "*.$from" -exec sh -c "mv "$3" "${3%.$1}.$2"" sh "$from" "$to" {} ";"
Wewnątrz inte rnal script, $1
będzie ciągiem text
, $2
będzie ciągiem txt
i $3
byłyby dowolną ścieżką znalezioną przez find
. Rozwinięcie parametru ${3%.$1}
pobrałoby nazwę ścieżki i usunąłoby z niej przyrostek .text
.
Lub używając dirname
/ basename
:
find . -type f -name "*.$from" -exec sh -c " mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"" sh "$from" "$to" {} ";"
lub z dodanymi zmiennymi w skrypt wewnętrzny:
find . -type f -name "*.$from" -exec sh -c " from=$1; to=$2; pathname=$3 mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"" sh "$from" "$to" {} ";"
Zwróć uwagę, że w tej ostatniej odmianie zmienne from
i to
w powłoce potomnej różnią się od zmiennych o takich samych nazwach w zewnętrznym skrypcie.
Powyższe jest poprawnym sposobem wywołania dowolnego złożonego skryptu z -exec
z find
. Używanie find
w pętli takiej jak
for pathname in $( find ... ); do
jest podatne na błędy i nieeleganckie (osobiste zdanie). Dzieli nazwy plików na białe znaki, wywołuje globalizację nazw plików, a także zmusza powłokę do rozwinięcia pełnego wyniku find
przed uruchomieniem pierwszej iteracji pętli.
Zobacz też:
- Dlaczego pętla na wyjściu find ' jest niewłaściwa?
- Czy można bezpiecznie używać polecenia `find -exec sh -c`?
Używając -exec ... {} +
;
na końcu można zastąpić +
. Powoduje to, że find
wykonuje dane polecenie z jak największą liczbą argumentów (znalezionych nazw ścieżek), a nie raz dla każdej znalezionej ścieżki. Aby to zadziałało, ciąg {}
musi wystąpić tuż przed +
.
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec cat {} +
Tutaj find
zbierze otrzymane nazwy ścieżek i wykona cat
na jak największej liczbie z nich naraz.
find . -type f -name "*.txt" \ -exec grep -q "hello" {} ";" \ -exec mv -t /tmp/files_with_hello/ {} +
Podobnie, mv
zostanie wykonane jako kilka razy, jak to możliwe. Ten ostatni przykład wymaga GNU mv
z coreutils (który obsługuje opcję -t
).
Używanie -exec sh -c ... {} +
to także skuteczny sposób na zapętlenie zestawu nazw ścieżek z dowolnie złożonym skryptem.
Podstawy są takie same, jak w przypadku używania -exec sh -c ... {} ";"
, ale skrypt przyjmuje teraz znacznie dłuższą listę argumentów. Można je zapętlić, wykonując pętlę nad "$@"
wewnątrz skryptu.
Nasz przykład z ostatniej sekcji, która zmienia sufiksy nazw plików:
from=text # Find files that have names like something.text to=txt # Change the .text suffix to .txt find . -type f -name "*.$from" -exec sh -c " from=$1; to=$2 shift 2 # remove the first two arguments from the list # because in this case these are *not* pathnames # given to us by find for pathname do # or: for pathname in "$@"; do mv "$pathname" "${pathname%.$from}.$to" done" sh "$from" "$to" {} +
Używanie -execdir
Istnieje również -execdir
( implementowane przez większość wariantów find
, ale nie jest to opcja standardowa).
Działa to jak -exec
z tą różnicą, że dane polecenie powłoki jest wykonywane z katalogiem o znalezionej ścieżce jako bieżącym katalogiem roboczym i że będzie zawierało nazwę podstawową znalezionej ścieżki bez ścieżki (ale GNU find
będzie nadal poprzedzać nazwę basenową znakiem ./
, podczas gdy BSD find
tego nie zrobi).
Przykład:
find . -type f -name "*.txt" \ -execdir mv {} done-texts/{}.done \;
Spowoduje to przeniesienie każdego znalezionego *.txt
-pliku do wcześniej istniejącego done-texts
podkatalogu w tym samym katalogu, w którym plik był znaleziono . Nazwa pliku zostanie również zmieniona przez dodanie do niego przyrostka .done
.
Byłoby to nieco trudniejsze w przypadku -exec
, ponieważ musielibyśmy pobrać nazwę podstawową znalezionego pliku z {}
, aby utworzyć nową nazwę pliku. Potrzebujemy również nazwy katalogu z {}
, aby poprawnie zlokalizować katalog done-texts
.
Z -execdir
, niektóre takie rzeczy stają się łatwiejsze.
Odpowiednia operacja przy użyciu -exec
zamiast -execdir
musiałby zastosować powłokę potomną:
find . -type f -name "*.txt" -exec sh -c " for name do mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done" done" sh {} +
lub
find . -type f -name "*.txt" -exec sh -c " for name do mv "$name" "${name%/*}/done-texts/${name##*/}.done" done" sh {} +
Komentarze
-
-exec
pobiera program i argumenty i uruchamia go; niektóre polecenia powłoki składają się tylko z programu i argumentów, ale wiele z nich nie. Polecenie powłoki może obejmować przekierowanie i orurowanie;-exec
nie może (chociaż całefind
można przekierować). Polecenie powłoki może używać; && if
itp.;-exec
nie może, chociaż-a -o
może coś zrobić. Polecenie powłoki może być aliasem, funkcją powłoki lub funkcją wbudowaną;-exec
nie może. Polecenie powłoki może rozwinąć vars;-exec
nie może (chociaż zewnętrzna powłoka obsługującafind
może). Polecenie powłoki może za każdym razem zastąpić$(command)
inaczej;-exec
nie może. … - Mówiąc to ' polecenie powłoki jest tutaj nieprawidłowe,
find -exec cmd arg \;
nie ' t wywołuje powłokę w celu zinterpretowania wiersza poleceń powłoki, uruchamia onaexeclp("cmd", "arg")
bezpośrednio, a nieexeclp("sh", "-c", "cmd arg")
(dla którego Powłoka zrobiłaby odpowiednikexeclp("cmd", "arg")
, gdybycmd
nie zostało wbudowane). - Możesz wyjaśnić, że wszystko argumenty
find
po-exec
i do;
lub+
tworzy polecenie do wykonania wraz z jego argumentami, z każdym wystąpieniem argumentu{}
zastępowanym bieżącym plikiem (z;
) i{}
jako ostatni argument przed+
zastąpionymi listą plików jako oddzielnymi argumentami (w{} +
case). IOW-exec
przyjmuje kilka argumentów, zakończonych;
lub{}
+
. - @Kusalananda Wouldn ' t Twój ostatni przykład działa również z tym prostszym poleceniem:
find . -type f -name '*.txt' -exec sh -c "mv $1 $(dirname $1)/done-texts/$(basename $1).done" sh {} ';'
? - @Atralb Tak, to również zadziałałoby i miałby taki sam efekt jak ostatni fragment kodu, ale zamiast uruchamiać
mv
w pętli, raz na znaleziony plik, wykonujesz zarównosh
, jak imv
dla każdego znalezionego pliku, co będzie zauważalnie wolniej w przypadku dużych ilości plików.
man
czyta nazwa_narzędzia lub argument zawierające tylko dwa znaki " {} " należy zastąpić d przez obecną ścieżkę , która wydaje mi się wystarczająca. Dodatkowo ma przykład z-exec rm {} \;
, tak jak w twoim pytaniu. Za moich czasów nie było prawie żadnych innych zasobów niż " duża szara ściana ", drukowane książkiman
stron (papier był tańszy niż przechowywanie). Więc wiem, że to wystarczy dla kogoś nowego w temacie. Możesz zadać ostatnie pytanie tutaj. Niestety ani @Kusalananda, ani ja nie mamy na to odpowiedzi.