Jak sprawić, by szukanie nie powiodło się, jeśli -exec się nie powiedzie?

29

Kiedy uruchamiam to polecenie w powłoce (w niepustym katalogu):

find . -exec invalid_command_here {} \;

Rozumiem:

find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory
find: invalid_command_here: No such file or directory

(i tak dalej dla każdego pliku)

Muszę findponieść porażkę po pierwszym błędzie. Czy jest jakiś sposób, żeby to zadziałało? Nie mogę użyć xargs, ponieważ mam spacje na ścieżce, ale potrzebuję skryptu wywołującego ten kod, aby zwrócić kod błędu.

Steven Fisher
źródło

Odpowiedzi:

34

To ograniczenie find. Standard POSIX określa, że ​​zwracanym statusem findjest 0, chyba że wystąpił błąd podczas przeglądania katalogów; status powrotu wykonanych poleceń nie wchodzi w to.

Możesz sprawić, aby polecenia zapisywały swój status do pliku lub do deskryptora:

find_status_file=$(mktemp findstatus)
: >"$find_status_file"
find  -exec sh -c 'trap "echo \$?" EXIT; invalid_command "$0"' {} \;
if [ -s "$find_status_file" ]; then
  echo 1>&2 "An error occurred"
fi
rm -f "$find_status_file"

Inną metodą, jak odkryłeś , jest użycie xargs. Te xargspolecenia zawsze przetwarza wszystkie pliki, ale zwraca status 1 Jeśli jakieś polecenie zwraca stan niezerowe.

find  -print0 | xargs -0 -n1 invalid_command

Jeszcze inną metodą jest unikanie findi stosowanie rekurencyjnego globowania w powłoce zamiast: **/oznacza dowolną głębokość podkatalogów. Wymaga to wersji 4 lub wyższej bash; macOS utknął w wersji 3.x, więc musisz zainstalować go z kolekcji portów. Użyj, set -eaby zatrzymać skrypt dla pierwszego polecenia zwracającego niezerowy status.

shopt -s globstar
set -e
for x in **/*.xml; do invalid_command "$x"; done

Uwaga: w wersjach bash 4.0 do 4.2 działa to, ale przegląda dowiązania symboliczne do katalogów, co zwykle nie jest pożądane.

Jeśli użyjesz zsh zamiast bash, rekurencyjne globowanie działa natychmiast po wyjęciu z pudełka bez żadnych błędów. Zsh jest domyślnie dostępny na OSX / macOS. W Zsh możesz po prostu pisać

set -e
for x in **/*.xml; do invalid_command "$x"; done
Gilles „SO- przestań być zły”
źródło
xargsPodejście działa w ogóle, ale w jakiś sposób łamie na bash -cpolecenia. Na przykład: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Jest to wykonywane wielokrotnie, podczas gdy find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}jest wykonywane raz i kończy się niepowodzeniem. Masz pomysł, dlaczego?
DKroot
@DKroot Nigdy nie używaj w {}środku bash -c. Pobiera nazwę pliku i wstawia go bezpośrednio do polecenia powłoki. Jeśli nazwa pliku zawiera znaki, które mają specjalne znaczenie w powłoce, takie jak spacje, powłoka interpretuje te znaki specjalne jako takie. Jeśli potrzebujesz powłoki, przekaż ją {}jako osobny argument, np. bash -c 'foo "$0"' {}(Zwróć również uwagę na podwójne cudzysłowy $0).
Gilles „SO- przestań być zły”
OK, cytując pytania, dlaczego poniższe informacje nie kończą się na pierwszym błędzie? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot
@DKroot Dlaczego zatrzymałby się na błędzie? xargs zawsze uruchamia polecenie na wszystkich elementach.
Gilles „SO- przestań być zły”
Próbuję użyć tej odpowiedzi: find . -print0 | xargs -0 -n1 invalid_commandpodejście xargs ( ). Zatrzymuje się na pierwszym błędem poprawnie: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. Świetny! Ale to samo podejście nie działa z bash -c(powyżej). Jedyna różnica między nimi jest bash -c.
DKroot
18

Zamiast tego mogę użyć tego:

find . -name *.xml -print0 | xargs -n 1 -0 invalid_command
Steven Fisher
źródło
4

xargsjest jedną z opcji. Jednak w rzeczywistości jest to banalnie łatwe, findużywając również +zamiast\;

-exec  utility_name  [argument ...]   {} +

Z dokumentacji POSIX :

Jeśli wyrażenie podstawowe jest interpunkcyjne znakiem plus, pierwiastek zawsze ocenia się jako prawdziwy, a nazwy ścieżek, dla których pierwiastek jest oceniany, są agregowane w zestawy. Narzędzie nazwa_użyteczności należy wywołać raz dla każdego zestawu zagregowanych nazw ścieżek. Każde wywołanie rozpocznie się po agregacji ostatniej nazwy ścieżki w zestawie i zostanie zakończone przed zakończeniem działania narzędzia find oraz przed agregacją pierwszej nazwy ścieżki w następnym zestawie (jeśli istnieje) dla tego podstawowego, ale poza tym nie jest określone, czy wywołanie występuje przed, podczas lub po ocenach innych podstawowych. Jeżeli jakiekolwiek wywołanie zwraca niezerową wartość jako status wyjścia, program narzędziowy find zwraca niezerowy status wyjścia.Argument zawierający tylko dwa znaki „{}” należy zastąpić zestawem zagregowanych nazw ścieżek, przy czym każda nazwa ścieżki przekazywana jest jako osobny argument do wywoływanego narzędzia w tej samej kolejności, w jakiej została zagregowana. Rozmiar dowolnego zestawu dwóch lub więcej ścieżek powinien być ograniczony tak, aby wykonanie narzędzia nie powodowało przekroczenia limitu {ARG_MAX} systemu. Jeśli występuje więcej niż jeden argument zawierający tylko dwa znaki „{}”, zachowanie jest nieokreślone.

szwajcarski
źródło