Como faço para encontrar a falha se -exec falhar?

26

Quando executo este comando no shell (em um diretório não vazio):

find . -exec invalid_command_here {} \;

Eu entendi isso:

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

(e assim por diante para cada arquivo)

Preciso findfalhar após o primeiro erro. Existe alguma maneira de fazer isso funcionar? Não posso usar xargs, pois tenho espaços no meu caminho, mas preciso do script que chama isso para retornar um código de erro.

Steven Fisher
fonte

Respostas:

31

Esta é uma limitação de find. O padrão POSIX especifica que o status de retorno findé 0, a menos que ocorreu um erro ao percorrer os diretórios; o status de retorno dos comandos executados não entra nele.

Você pode fazer com que os comandos gravem seus status em um arquivo ou em um descritor:

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"

Outro método, como você descobriu , é usar xargs. Os xargscomandos sempre processam todos os arquivos, mas retornam o status 1 se algum dos comandos retornar um status diferente de zero.

find  -print0 | xargs -0 -n1 invalid_command

Ainda outro método é evitar finde usar globbing recursivo no shell: **/significa qualquer profundidade de subdiretórios. Isso requer a versão 4 ou superior do bash; O macOS está bloqueado na versão 3.x, portanto você precisa instalá-lo a partir de uma coleção de portas. Use set -epara interromper o script no primeiro comando, retornando um status diferente de zero.

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

Observe que, no bash 4.0 a 4.2, isso funciona, mas percorre links simbólicos para diretórios, o que geralmente não é desejável.

Se você usar o zsh em vez do bash, o globbing recursivo funciona imediatamente, sem truques. O Zsh está disponível por padrão no OSX / macOS. No zsh, você pode simplesmente escrever

set -e
for x in **/*.xml; do invalid_command "$x"; done
Gilles
fonte
A xargsabordagem funciona em geral, mas de alguma forma quebra os bash -ccomandos. Por exemplo: find . -name '*.xml' -print0 | xargs -0 -n 1 -I '{}' bash -c "foo {}". Isso é executado várias vezes, enquanto find . -name '2*.xml' -print0 | xargs -0 -n 1 -I '{}' foo {}é executado uma vez e falha. Alguma idéia do porquê?
DKroot 11/03
@DKroot Nunca use {}dentro bash -c. Isso pega o nome do arquivo e o insere diretamente dentro do comando shell. Se o nome do arquivo contiver caracteres que tenham um significado especial no shell, como espaços, o shell interpretará esses caracteres especiais como tal. Se você precisar de um shell, passe {}como um argumento separado, por exemplo bash -c 'foo "$0"' {}(observe também as aspas duplas $0).
Gilles
OK, citando perguntas à parte, por que o seguinte não para no primeiro erro? find . -name '*' -print0 | xargs -0 -n 1 -I '{}' bash -c 'foo "$0"' {}
DKroot 27/03
@DKroot Por que isso iria parar em um erro? O xargs sempre executa o comando em todos os itens.
Gilles
Estou tentando usar esta resposta: a find . -print0 | xargs -0 -n1 invalid_commandabordagem xargs ( ). Este pára no primeiro erro corretamente: find . -name '*' -print0 | xargs -0 -n 1 -I '{}' foo {}. Ótimo! Mas a mesma abordagem não funciona bash -c(acima). A única diferença entre os dois é bash -c.
DKroot 28/03
18

Eu posso usar isso:

find . -name *.xml -print0 | xargs -n 1 -0 invalid_command
Steven Fisher
fonte
3

xargsé uma opção. No entanto, também é trivialmente fácil fazer isso findusando, em +vez de \;:

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

Na documentação do POSIX :

Se a expressão primária for pontuada por um sinal de mais, a primária sempre será avaliada como verdadeira e os nomes de caminho para os quais a primária é avaliada serão agregados em conjuntos. O utilitário utility_name deve ser chamado uma vez para cada conjunto de nomes de caminho agregados. Cada chamada começará após a agregação do último nome do caminho no conjunto e deverá ser concluída antes da saída do utilitário find e antes do primeiro nome do caminho no próximo conjunto (se houver) ser agregado para esse primário, mas, caso contrário, não será especificado se a chamada ocorre antes, durante ou após as avaliações de outras primárias. Se qualquer chamada retornar um valor diferente de zero como status de saída, o utilitário find retornará um status de saída diferente de zero.Um argumento contendo apenas os dois caracteres “{}” deve ser substituído pelo conjunto de nomes de caminho agregados, com cada nome de caminho passado como um argumento separado para o utilitário chamado na mesma ordem em que foi agregado. O tamanho de qualquer conjunto de dois ou mais nomes de caminho deve ser limitado, de modo que a execução do utilitário não faça com que o limite de {ARG_MAX} do sistema seja excedido. Se mais de um argumento contendo apenas os dois caracteres “{}” estiver presente, o comportamento não será especificado.

suíço
fonte