Jak mogę uruchomić określone polecenie dla każdego wyniku wyszukiwania?

49

Jak uruchomić określone polecenie dla każdego pliku znalezionego za pomocą findpolecenia? Na potrzeby pytania powiedzmy, że chciałbym po prostu usunąć każdy plik znaleziony przez find.

FailedDev
źródło

Odpowiedzi:

60

Edycja: Chociaż poniższa odpowiedź wyjaśnia ogólny przypadek użycia, należy zauważyć, że usuwanie plików i katalogów jest szczególnym przypadkiem. Zamiast używać -execdir rm {} \;konstrukcji, po prostu użyj -delete, jak w:

find -iname '*.txt' -delete

Zajmuje się to wieloma przypadkowymi przypadkami, o których możesz nie myśleć, włączając w to pliki zamówień i katalogi, które należy usunąć, aby nie popełnić błędów. W innych przypadkach użycia ...

Najlepszym sposobem radzenia sobie z uruchomionymi komendami wyników wyszukiwania jest zwykle użycie różnych -execopcji findpolecenia. W szczególności powinieneś próbować używać, -execdirgdy tylko jest to możliwe, ponieważ działa on w katalogu znalezionego pliku i jest ogólnie bezpieczniejszy (w sensie zapobiegania katastrofalnym błędom), niż inne opcje.

Po -execopcjach pojawia się polecenie, które chcesz uruchomić z {}oznaczeniem miejsca, w którym plik znaleziony przez find powinien zostać dołączony, a kończy je albo \;uruchomienie polecenia raz dla każdego pliku, albo +zastąpienie {}listą argumentów wszystkich dopasowań . Zauważ, że terminator średnika jest ucieczkowy, więc powłoka nie rozumie, że jest separatorem prowadzącym do nowej komendy.

Powiedzmy, że znalazłeś wszystkie pliki tekstowe:

find -iname '*.txt' -execdir rm {} \;

Oto odpowiedni bit z instrukcji manualnej ( man find):

   -exec command ;
          Execute  command;  true  if 0 status is returned.  All following
          arguments to find are taken to be arguments to the command until
          an  argument  consisting of ‘;’ is encountered.  The string ‘{}’
          is replaced by the current file name being processed  everywhere
          it occurs in the arguments to the command, not just in arguments
          where it is alone, as in some versions of find.  Both  of  these
          constructions might need to be escaped (with a ‘\’) or quoted to
          protect them from expansion by the shell.  See the EXAMPLES sec-
          tion for examples of the use of the -exec option.  The specified
          command is run once for each matched file.  The command is  exe-
          cuted  in  the starting directory.   There are unavoidable secu-
          rity problems surrounding use of the -exec  action;  you  should
          use the -execdir option instead.


   -exec command {} +
          This  variant  of the -exec action runs the specified command on
          the selected files, but the command line is built  by  appending
          each  selected file name at the end; the total number of invoca-
          tions of the command will  be  much  less  than  the  number  of
          matched  files.   The command line is built in much the same way
          that xargs builds its command lines.  Only one instance of  ‘{}’
          is  allowed  within the command.  The command is executed in the
          starting directory.


   -execdir command ;

   -execdir command {} +
          Like -exec, but the specified command is run from the  subdirec-
          tory  containing  the  matched  file,  which is not normally the
          directory in which you started find.  This a  much  more  secure
          method  for invoking commands, as it avoids race conditions dur-
          ing resolution of the paths to the matched files.  As  with  the
          -exec action, the ‘+’ form of -execdir will build a command line
          to process more than one matched file, but any given  invocation
          of command will only list files that exist in the same subdirec-
          tory.  If you use this option, you must ensure that  your  $PATH
          environment  variable  does  not  reference  ‘.’;  otherwise, an
          attacker can run any commands they like by leaving an  appropri-
          ately-named  file in a directory in which you will run -execdir.
          The same applies to having entries in $PATH which are  empty  or
          which are not absolute directory names.
Caleb
źródło
Polecenie „znajdź” dostarczone przez Busybox nie obsługuje opcji -execdir, dlatego może być konieczne użycie jednej z metod potoku / xargs wymienionych poniżej.
MikeW,
9

Alternatywą jest potokowanie wyniku i parsowanie go za pomocą kolejnych poleceń. Jedynym bezpiecznym sposobem na to jest skorzystanie z -print0opcji, która mówi findo użyciu znaku pustego jako separatora wyników. Polecenia odbierające muszą mieć zdolność rozpoznawania danych wejściowych rozdzielanych znakami zerowymi. Przykład:

find /home/phunehehe -iregex '.*\.png$' -print0 | xargs -0 file

Zauważ, że -0opcja mówi, xargsaby traktować dane wejściowe jako rozdzielone znakiem null.

phunehehe
źródło
Możesz mieć -execwięcej plików, jeśli zakończysz je +zamiast ;. Zobacz odpowiedź Caleba.
Kevin
@Kevin masz rację, zaktualizowałem odpowiedź.
phunehehe,
3

Znajdź ma wbudowane polecenie usuwania, jeśli to wszystko, co musisz zrobić.

find . -name "*.txt" -delete

Każdy znaleziony plik .txt zostanie usunięty za pomocą powyższego polecenia.

SaultDon
źródło
2

Szukałem odpowiedzi na to i natknąłem się na ten wątek. Odpowiedzi dały mi pomysł, jak to osiągnąć. Załóżmy, że chcesz znaleźć mediainfowszystkie pliki JPEG

Dopisuje się to mediainfo "na początku i "na końcu każdego dopasowanego pliku (Aby maksymalnie uciec od znaków specjalnych), umieść go w skrypcie i uruchom skrypt:

find . -name *.jpg | sed -e 's/^/mediainfo "/g;' | sed -e 's/$/"/g;' > foo.sh && sh foo.sh

Jeśli martwisz się, że coś może pójść nie tak, możesz pominąć przekierowanie wyjścia do pliku i po prostu zobaczyć wynik w terminalu przed uruchomieniem skryptu.

RuMAN S.
źródło
1

Możesz to zrobić za pomocą xargspolecenia. xargszasadniczo uruchamia polecenie raz dla każdej instrukcji standardowego wejścia. Jeśli więc chcesz na przykład usunąć wszystkie .jpgpliki z katalogu, w wierszu polecenia możesz szybko:

$ find ./ -name "*.jpg" | xargs rm 

Aby to zrobić, możesz także użyć strzałki wstecz (nad przyciskiem Tab) (zwróć uwagę, że jest to znak cudzysłowu, a nie pojedynczy znak cudzysłowu):

$ rm `find ./ -name "*.jpg"`

Zauważ, że ze względu na sposób, w jaki xargspowłoki przetwarzają dane wejściowe, metoda xargs działa tylko wtedy, gdy żadna z nazw plików i nazw katalogów nie zawiera białych znaków ani żadnej z nich \"'; metoda backquote działa tylko wtedy, gdy żadna z nazw plików i katalogów nie zawiera białych znaków ani żadnej z nich \[?*.

IG83
źródło
3
Obie te metody są potencjalnie bardzo niebezpieczne, w szczególności metoda backtick. Istnieje wiele potencjalnych problemów ze znakami ucieczki w nazwach plików, które mogą spowodować uszkodzenie tych metod.
Caleb
1
Rozumiem, o co ci chodzi, ale tych poleceń można również używać razem z innymi narzędziami oprócz find, więc myślę, że warto o nich wspomnieć.
IG83
Być może warto o nich wspomnieć, ale kiedy ich nie używać, należy to określić. Pytanie PO tutaj konkretnie dotyczyło obsługi wyjścia find, co -execjest ogólnie lepszym rozwiązaniem. Jeśli chcesz określić je jako alternatywne, przynajmniej wyjaśnij, jak używać find -print0 | xargs -0do bezpiecznego obchodzenia się z nazwami plików i opracuj, kiedy należy zachować ostrożność w przypadku odwrotnych działań.
Caleb
1
Przy okazji, witam na stronie, widzę, że to twoja pierwsza odpowiedź. Przepraszam, że skoczyłem na całość. Ważne jest, aby uczyć ludzi o problemach, aby nie popełnili błędów, które trudno będzie później złapać, ale wciąż pamiętam dni, w których nie rozumiałem, dlaczego był to tak duży problem, więc proszę nie nie przyjmuj korekty jako osobistej.
Caleb
3
Dziekuję za przywitanie! Oczywiście nie ma twardych uczuć, z pewnością masz rację, ponieważ -wybór jest odpowiednim sposobem na poradzenie sobie z tym. Naprawdę zaczynam widzieć, jak wspaniała jest ta platforma, uczysz się nowych rzeczy nawet na podstawie komentarzy :)
IG83,