Ta odpowiedź składa się z następujących części:
- Podstawowe użycie
-exec
- Używanie
-exec
w połączeniu zsh -c
- Za pomocą
-exec ... {} +
- Za pomocą
-execdir
Podstawowe użycie -exec
-exec
Opcja bierze narzędzia zewnętrznego z opcjonalnymi argumentami jako argument i wykonuje go.
Jeśli ciąg {}
występuje w dowolnym miejscu danego polecenia, każde jego wystąpienie zostanie zastąpione aktualnie przetwarzaną nazwą ścieżki (np ./some/path/FILENAME
.). W większości powłok te znaki {}
nie muszą być cytowane.
Polecenie musi zostać zakończone znakiem „ ;
for”, find
aby wiedzieć, gdzie się ono kończy (ponieważ mogą pojawić się dalsze opcje). Aby chronić ;
powłokę przed powłoką, należy ją cytować jako \;
lub ';'
, w przeciwnym razie powłoka zobaczy ją jako koniec find
polecenia.
Przykład ( \
na końcu pierwszych dwóch linii są tylko kontynuacje linii):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
Znajduje to wszystkie zwykłe pliki ( -type f
), których nazwy pasują do wzorca *.txt
w bieżącym katalogu lub poniżej. Następnie przetestuje, czy ciąg hello
występuje w dowolnym ze znalezionych plików przy użyciu grep -q
(który nie generuje żadnych danych wyjściowych, tylko status wyjścia). Dla tych plików, które zawierają ciąg, cat
zostaną wykonane w celu wyprowadzenia zawartości pliku do terminala.
Każdy -exec
działa również jak „test” na znalezionych ścieżkach find
, podobnie jak -type
i -name
robi. Jeśli polecenie zwraca zerowy status wyjścia (oznaczający „sukces”), find
rozważana jest kolejna część polecenia, w przeciwnym razie find
polecenie będzie kontynuowane z następną nazwą ścieżki. Jest to używane w powyższym przykładzie, aby znaleźć pliki zawierające ciąg znaków hello
, ale zignorować wszystkie inne pliki.
Powyższy przykład ilustruje dwa najczęstsze przypadki użycia -exec
:
- Jako test do dalszego ograniczenia wyszukiwania.
- Aby wykonać jakąś akcję na znalezionej nazwie ścieżki (zwykle, ale niekoniecznie, na końcu
find
polecenia).
Używanie -exec
w połączeniu zsh -c
Polecenie, które -exec
można wykonać, jest ograniczone do zewnętrznego narzędzia z opcjonalnymi argumentami. Bezpośrednie używanie wbudowanych powłok, funkcji, warunków, potoków, przekierowań itp. -exec
Nie jest możliwe, chyba że jest zapakowane w coś w rodzaju sh -c
powłoki potomnej.
Jeśli bash
wymagane są funkcje, użyj bash -c
zamiast sh -c
.
sh -c
działa /bin/sh
ze skryptem podanym w wierszu poleceń, a następnie opcjonalnymi argumentami wiersza poleceń dla tego skryptu.
Prosty przykład użycia sh -c
samego, bez find
:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
To przekazuje dwa argumenty do skryptu powłoki potomnej:
Ciąg sh
. Będzie to dostępne $0
w skrypcie, a jeśli wewnętrzna powłoka wyświetli komunikat o błędzie, poprzedzi go tym ciągiem.
Argument apples
jest dostępna $1
w skrypcie, a gdyby nie było więcej argumentów, to te byłyby dostępne $2
, $3
itd. Będą one również dostępne na liście "$@"
(z wyjątkiem $0
, który nie będzie częścią "$@"
).
Jest to przydatne w połączeniu z, -exec
ponieważ pozwala nam tworzyć dowolnie złożone skrypty, które działają na ścieżki znalezione przez find
.
Przykład: Znajdź wszystkie zwykłe pliki, które mają określony sufiks nazwy pliku, i zmień ten sufiks nazwy pliku na inny, gdzie sufiksy 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 wewnętrznego skryptu $1
będzie ciąg znaków text
, $2
ciąg znaków txt
i $3
będzie to dowolna find
znaleziona dla nas nazwa ścieżki . Rozszerzenie parametru ${3%.$1}
wziąłoby nazwę ścieżki i usunęło .text
z niej przyrostek .
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 skrypcie wewnętrznym:
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" {} ';'
Zauważ, że w tej ostatniej odmianie zmienne from
i to
powłoka potomna różnią się od zmiennych o tych samych nazwach w skrypcie zewnętrznym.
Powyżej jest prawidłowy sposób wywoływania dowolnego skryptu złożonego -exec
z find
. Używanie find
w pętli jak
for pathname in $( find ... ); do
jest podatny na błędy i nieelegancki (opinia osobista). Dzieli nazwy plików na białe znaki, wywołuje globbing nazw plików, a także zmusza powłokę do rozwinięcia pełnego wyniku find
przed uruchomieniem pierwszej iteracji pętli.
Zobacz też:
Za pomocą -exec ... {} +
Na ;
końcu można zastąpić +
. Powoduje find
to wykonanie podanej komendy z jak największą liczbą argumentów (znalezionych nazw ścieżek), a nie jeden raz dla każdej znalezionej nazwy ścieżki. Ciąg {}
musi wystąpić tuż przed tym, +
aby to zadziałało .
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
Tutaj find
zbierze powstałe nazwy ścieżek i wykona je cat
na jak największej liczbie jednocześnie.
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
Podobnie w tym przypadku mv
zostanie wykonany tak mało, jak to możliwe. Ten ostatni przykład wymaga GNU mv
z coreutils (który obsługuje tę -t
opcję).
Użycie -exec sh -c ... {} +
jest również skutecznym sposobem na zapętlenie zestawu ścieżek za pomocą dowolnego, złożonego skryptu.
Podstawy są takie same jak przy użyciu -exec sh -c ... {} ';'
, ale skrypt zajmuje teraz znacznie dłuższą listę argumentów. Można je zapętlać, zapętlając "$@"
w skrypcie.
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" {} +
Za pomocą -execdir
Istnieje również -execdir
(zaimplementowane przez większość find
wariantów, ale nie jest to standardowa opcja).
Działa to -exec
z tą różnicą, że dane polecenie powłoki jest wykonywane z katalogiem znalezionej nazwy ścieżki jako bieżącym katalogiem roboczym, który {}
będzie zawierał nazwę basenową znalezionej nazwy ścieżki bez jej ścieżki (ale GNU find
nadal będzie poprzedzać nazwę bazy ./
, 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 znaleziono plik . Nazwa pliku zostanie również zmieniona przez dodanie .done
do niego sufiksu .
Byłoby to nieco trudniejsze do zrobienia, -exec
ponieważ musielibyśmy pobrać nazwę bazową znalezionego pliku, {}
aby utworzyć nową nazwę pliku. Potrzebujemy również nazwy katalogu, {}
aby done-texts
poprawnie zlokalizować katalog.
Z -execdir
niektórymi takimi rzeczami staje się łatwiejsze.
Odpowiednia operacja wykorzystująca -exec
zamiast -execdir
musiałaby użyć powłoki potomnej:
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 {} +