Как мне сделать find fail, если не работает -exec?

29

Когда я запускаю эту команду в оболочке (в непустом каталоге):

find . -exec invalid_command_here {} \;

Я получаю это:

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

(и так далее для каждого файла)

Мне нужно findпотерпеть неудачу после первой ошибки. Есть ли способ заставить это работать? Я не могу использовать xargs, так как у меня есть пробелы в моем пути, но мне нужен скрипт, вызывающий это, чтобы вернуть код ошибки.

Стивен Фишер
источник

Ответы:

34

Это ограничение find. Стандарт POSIX указывает, что возвращаемое состояние findравно 0, если при обходе каталогов не произошла ошибка; статус возврата выполненных команд в него не входит.

Вы можете заставить команды записывать их статус в файл или в дескриптор:

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"

Как вы обнаружили , другой метод - использовать xargs. Эти xargsкоманды всегда обрабатывает все файлы, но возвращает состояние 1 , если какой - либо из команд возвращает статус нуля.

find  -print0 | xargs -0 -n1 invalid_command

Еще один метод заключается в том, чтобы избегать findи использовать рекурсивное сглаживание в оболочке: **/означает любую глубину подкаталогов. Это требует версии 4 или выше bash; MacOS застрял на версии 3.x, поэтому вам придется установить его из коллекции портов. Используйте set -eдля остановки сценария в первой команде, возвращающей ненулевой статус.

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

Помните, что в bash 4.0 до 4.2 это работает, но проходит через символические ссылки на каталоги, что обычно нежелательно.

Если вы используете zsh вместо bash, рекурсивное сглаживание работает из коробки без ошибок. Zsh доступен по умолчанию в OSX / macOS. В зш можно просто написать

set -e
for x in **/*.xml; do invalid_command "$x"; done
Жиль "ТАК - перестань быть злым"
источник
xargsПодход работает в целом , но как - то ломает на bash -cкоманды. Например: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Это выполняется несколько раз, тогда find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}как выполняется один раз и завершается ошибкой. Есть идеи почему?
DKroot
@DKroot Никогда не используйте {}внутри bash -c. Это берет имя файла и вставляет его непосредственно в команду оболочки. Если имя файла содержит символы, которые имеют особое значение в оболочке, например пробелы, оболочка интерпретирует эти специальные символы как таковые. Если вам нужна оболочка, передайте {}в качестве отдельного аргумента, например bash -c 'foo "$0"' {}(также обратите внимание на двойные кавычки $0).
Жиль "ТАК - перестань быть злым"
Хорошо, оставив в стороне вопросы, почему следующее не останавливается на первой ошибке ?? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot
@DKroot Почему бы остановиться на ошибке? xargs всегда запускает команду для всех элементов.
Жиль "ТАК - перестань быть злым"
Я пытаюсь использовать этот ответ: подход xargs ( find . -print0 | xargs -0 -n1 invalid_command). Это останавливается на первой ошибки правильно: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. Большой! Но тот же подход не работает с bash -c(выше). Единственная разница между этими двумя bash -c.
DKroot
18

Я могу использовать это вместо:

find . -name *.xml -print0 | xargs -n 1 -0 invalid_command
Стивен Фишер
источник
4

xargsэто один из вариантов. Тем не менее, это на самом деле тривиально легко сделать это find, используя +вместо\;

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

Из документации POSIX :

Если первичное выражение акцентировано знаком плюс, первичное всегда должно оцениваться как истина, а имена путей, для которых оценивается первичное, должны быть объединены в наборы. Утилита utility_name должна вызываться один раз для каждого набора агрегированных путей. Каждый вызов должен начинаться после агрегирования последнего пути в наборе и должен завершаться до выхода утилиты поиска и до агрегирования первого пути в следующем наборе (если есть) для этого первичного, но в противном случае не указано, является ли вызов происходит до, во время или после оценки других праймериз. Если какой-либо вызов возвращает ненулевое значение в качестве состояния выхода, утилита поиска возвращает ненулевое состояние выхода.Аргумент, содержащий только два символа «{}», должен быть заменен набором агрегированных имен путей, причем каждое имя пути передается в качестве отдельного аргумента вызываемой утилите в том же порядке, в котором она была агрегирована. Размер любого набора из двух или более путей должен быть ограничен таким образом, чтобы выполнение утилиты не приводило к превышению системного лимита {ARG_MAX}. Если присутствует более одного аргумента, содержащего только два символа «{}», поведение не определено.

швейцарцы
источник