Zrozumienie opcji -exec polecenia `find`

53

Ciągle szukam składni

find . -name "FILENAME"  -exec rm {} \;

głównie dlatego, że nie widzę, jak dokładnie -execdziała ta część. Co oznaczają nawiasy klamrowe, ukośnik odwrotny i średnik? Czy istnieją inne przypadki użycia tej składni?

Zsolt Szilagy
źródło
11
@Philippos: Rozumiem twój punkt widzenia. Należy pamiętać, że strony podręcznika są odniesieniem, tj. Przydatne dla osób, które rozumieją sprawę, aby wyszukać składnię. Dla kogoś nowego w tym temacie są często tajemnicze i formalne, aby były przydatne. Przekonasz się, że zaakceptowana odpowiedź jest około 10 razy dłuższa niż wpis strony podręcznika, i to z jakiegoś powodu.
Zsolt Szilagy
6
Nawet stara manstrona POSIX brzmi : nazwa_użyteczności lub argument zawierający tylko dwa znaki „{}” należy zastąpić bieżącą nazwą ścieżki , co wydaje mi się wystarczające. Dodatkowo ma przykład -exec rm {} \;, tak jak w twoim pytaniu. W moich czasach nie było prawie żadnych innych zasobów niż „duża szara ściana”, książki z wydrukowanymi manstronami (papier był tańszy niż przechowywanie). Wiem więc, że to wystarczy dla kogoś nowego w temacie. Ostatnie pytanie można jednak zadać tutaj. Niestety, ani @Kusalananda, ani ja nie mamy na to odpowiedzi.
Philippos
1
Comeon @Philippos. Czy naprawdę mówisz Kusalanandzie, że nie poprawił strony? :-)
Zsolt Szilagy
1
@ allo Chociaż xargsczasem jest przydatny, findmoże przekazać wiele argumentów ścieżki do polecenia bez niego. -exec command... {} +( +zamiast zamiast \;) przechodzi tyle ścieżek, command...ile będzie pasować (każdy system operacyjny ma swój własny limit długości wiersza poleceń). I jak xargsThe +-zakończony forma find„s -execdziałania będzie również uruchomić command...kilka razy w rzadkich przypadkach, że istnieje zbyt wiele ścieżek, aby mieścić się w limicie.
Eliah Kagan,
2
@ZsoltSzilagy Nie powiedziałem tego ani nie miałem na myśli tego. Karmił cię bardzo dobrze, myślę, że jesteś wystarczająco dorosły, aby jeść samodzielnie. (-;
Philippos

Odpowiedzi:

90

Ta odpowiedź składa się z następujących części:

  • Podstawowe użycie -exec
  • Używanie -execw połączeniu zsh -c
  • Za pomocą -exec ... {} +
  • Za pomocą -execdir

Podstawowe użycie -exec

-execOpcja 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”, findaby 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 findpolecenia.

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 *.txtw bieżącym katalogu lub poniżej. Następnie przetestuje, czy ciąg hellowystę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, catzostaną wykonane w celu wyprowadzenia zawartości pliku do terminala.

Każdy -execdziała również jak „test” na znalezionych ścieżkach find, podobnie jak -typei -namerobi. Jeśli polecenie zwraca zerowy status wyjścia (oznaczający „sukces”), findrozważana jest kolejna część polecenia, w przeciwnym razie findpolecenie 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:

  1. Jako test do dalszego ograniczenia wyszukiwania.
  2. Aby wykonać jakąś akcję na znalezionej nazwie ścieżki (zwykle, ale niekoniecznie, na końcu findpolecenia).

Używanie -execw połączeniu zsh -c

Polecenie, które -execmoż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. -execNie jest możliwe, chyba że jest zapakowane w coś w rodzaju sh -cpowłoki potomnej.

Jeśli bashwymagane są funkcje, użyj bash -czamiast sh -c.

sh -cdziała /bin/shze skryptem podanym w wierszu poleceń, a następnie opcjonalnymi argumentami wiersza poleceń dla tego skryptu.

Prosty przykład użycia sh -csamego, bez find:

sh -c 'echo  "You gave me $1, thanks!"' sh "apples"

To przekazuje dwa argumenty do skryptu powłoki potomnej:

  1. Ciąg sh. Będzie to dostępne $0w skrypcie, a jeśli wewnętrzna powłoka wyświetli komunikat o błędzie, poprzedzi go tym ciągiem.

  2. Argument applesjest dostępna $1w skrypcie, a gdyby nie było więcej argumentów, to te byłyby dostępne $2, $3itd. 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, -execponieważ 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 $1będzie ciąg znaków text, $2ciąg znaków txti $3będzie to dowolna findznaleziona dla nas nazwa ścieżki . Rozszerzenie parametru ${3%.$1}wziąłoby nazwę ścieżki i usunęło .textz 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 fromi topowł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 -execz find. Używanie findw 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 findprzed uruchomieniem pierwszej iteracji pętli.

Zobacz też:


Za pomocą -exec ... {} +

Na ;końcu można zastąpić +. Powoduje findto 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 findzbierze powstałe nazwy ścieżek i wykona je catna 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 mvzostanie wykonany tak mało, jak to możliwe. Ten ostatni przykład wymaga GNU mvz coreutils (który obsługuje tę -topcję).

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ść findwariantów, ale nie jest to standardowa opcja).

Działa to -execz 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 findnadal będzie poprzedzać nazwę bazy ./, podczas gdy BSD findtego nie zrobi).

Przykład:

find . -type f -name '*.txt' \
    -execdir mv {} done-texts/{}.done \;

Spowoduje to przeniesienie każdego znalezionego *.txtpliku do wcześniej istniejącego done-textspodkatalogu w tym samym katalogu, w którym znaleziono plik . Nazwa pliku zostanie również zmieniona przez dodanie .donedo niego sufiksu .

Byłoby to nieco trudniejsze do zrobienia, -execponieważ musielibyśmy pobrać nazwę bazową znalezionego pliku, {}aby utworzyć nową nazwę pliku. Potrzebujemy również nazwy katalogu, {}aby done-textspoprawnie zlokalizować katalog.

Z -execdirniektórymi takimi rzeczami staje się łatwiejsze.

Odpowiednia operacja wykorzystująca -execzamiast -execdirmusiał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 {} +
Kusalananda
źródło
7
-execpobiera program i argumenty i uruchamia go; niektóre polecenia powłoki składają się tylko z programu i argumentów, ale wiele nie. Polecenie powłoki może obejmować przekierowanie i potokowanie; -execnie może (chociaż findmożna przekierować całość ). Polecenie powłoki może używać ; && ifetc; -execnie może, chociaż -a -omoże coś zrobić. Polecenie powłoki może być aliasem lub funkcją powłoki albo być wbudowane; -execNie mogę. Polecenie powłoki może rozszerzać zmienne; -execnie może (chociaż zewnętrzna powłoka, która uruchamia findpuszkę). Polecenie powłoki może za $(command)każdym razem zmieniać się inaczej; -execNie mogę. ...
dave_thompson_085
... Polecenie powłoki może globować, -execnie może - chociaż findmoże iterować pliki tak samo, jak większość globów, więc jest to rzadko potrzebne.
dave_thompson_085
@ dave_thompson_085 Oczywiście poleceniem powłoki może być shsama, co jest w pełni zdolne do robienia tych wszystkich rzeczy
Tavian Barnes
2
Mówiąc, że to polecenie powłoki jest tutaj niepoprawne, find -exec cmd arg \;nie wywołuje powłoki, aby zinterpretować wiersz polecenia powłoki, działa execlp("cmd", "arg")bezpośrednio, a nie execlp("sh", "-c", "cmd arg")(dla którego powłoka wykonałaby odpowiednik, execlp("cmd", "arg")gdyby cmdnie została wbudowana).
Stéphane Chazelas,
2
Można wyjaśnić, że wszystkie findargumenty po -execi do polecenia ;lub +tworzą polecenie do wykonania wraz z jego argumentami, z każdą instancją {}argumentu zamienioną na bieżący plik (z ;) i {}jako ostatni argument przed +zastąpiony listą plików jako osobne argumenty (w {} +przypadku). IOW -execprzyjmuje kilka argumentów, zakończonych znakiem ;lub {} +.
Stéphane Chazelas,