bash - posso fazer: encontrar ... -exec isto && aquilo?

23

Existe uma maneira de combinar logicamente dois comandos de shell que são chamados com o find - exec ?

Por exemplo, para imprimir todos os arquivos .csv que contêm a string foo juntamente com a ocorrência, eu gostaria de fazer:

find . -iname \*.csv -exec grep foo {} && echo {} \;

mas o bash reclama com "falta de argumento para '-exec'"

Marcus Junius Brutus
fonte
2
Você pode usar 2 -execem sequência ou usar um único -exec sh -c 'grep foo "$0" && printf %s\\n "$0"' {} \;.
Jw013
Isso me tropeçou repetidamente: sempre espero que o primeiro argumento passado sh(neste caso {}) seja $1e $0será algo como sh. Mas, na verdade, você está correto, o primeiro argumento aparece como $0. Ter o primeiro argumento como o nome do comando de chamada é apenas uma convenção, que não é aplicada automaticamente nesses casos.
dubiousjim
Talvez devem ser mesclados com esta pergunta: unix.stackexchange.com/q/18077/4801
dubiousjim

Respostas:

24

-execé um predicado que executa um comando (não um shell) e é avaliado como verdadeiro ou falso com base no resultado do comando (status de saída zero ou diferente de zero).

Tão:

find . -iname '*.csv' -exec grep foo {} \; -print

iria imprimir o caminho do arquivo se grepencontra foo no arquivo. Em vez de -printvocê pode usar outro -execpredicado ou qualquer outro predicado

find . -iname '*.csv' -exec grep foo {} \; -exec echo {} \;

Veja também os operadores !e -oencontre para negação e ou .

Como alternativa, você pode iniciar um shell como:

find . -iname '*.csv' -exec sh -c '
   grep foo "$1" && echo "$1"' sh {} \;

Ou, para evitar a necessidade de iniciar um shell para cada arquivo:

find . -iname '*.csv' -exec sh -c '
  for i do
    grep foo "$i" && echo "$i"
  done' sh {} +
Stéphane Chazelas
fonte
10

O problema que você está enfrentando é que o shell analisa primeiro a linha de comando e vê dois comandos simples separados pelo &&operador:, find . -iname \*.csv -exec grep foo {}e echo {} \;. Citando &&( find . -iname \*.csv -exec grep foo {} '&&' echo {} \;) ignora isso, mas agora o comando executado por findalgo como grepcom os argumentos foo, wibble.csv, &&, echoe wibble.csv. Você precisa instruir findpara executar um shell que interpretará o &&operador:

find . -iname \*.csv -exec sh -c 'grep foo "$0" && echo "$0"' {} \;

Observe que o primeiro argumento a seguir sh -c SOMECOMMANDé $0não $1.

Você pode economizar o tempo de inicialização de um processo de shell para cada arquivo agrupando as chamadas de comando -exec … +. Para facilitar o processamento, passe algum valor fictício $0para "$@"enumerar os nomes dos arquivos.

find . -iname \*.csv -exec sh -c 'for x in "$@"; do grep foo "$x" && echo "$x"; done' \ {} +

Se o comando shell tiver apenas dois programas separados por &&, ele findpoderá -execexecutar o trabalho sozinho: escreva duas ações consecutivas e a segunda será executada apenas se a primeira sair com o status 0.

find . -iname \*.csv -exec grep foo {} \; -exec echo {} \;

(Presumo que grepe echosão apenas para fins de ilustração, como -exec echopode ser substituído por -printe a saída resultante não é particularmente qualquer maneira útil.)

Gilles 'SO- parar de ser mau'
fonte
2
Costumo evitar o uso de "$ 0" para isso, pois também é usado pelo shell para exibir mensagens de erro. Por exemplo, você pode ver uma ./some-file: grep: command not foundmensagem de erro confusa . -exec find sh -c '... "$1"' sh {} \;não teria o problema. Há um erro de digitação (relacionado) no seu segundo comando find.
Stéphane Chazelas
3

Nesse caso específico, eu faria:

find . -iname \*.csv -exec grep -l foo \{\} \;

Ou se você concorda :

ack -al -G '.*\.csv' foo

Para responder à sua pergunta real, algo como isto pode funcionar:

find . -iname \*.csv -exec sh -c "grep foo {} && echo {}" \;

Dennis Kaarsemaker
fonte
3
Esse último comando find não é apenas não-portátil, mas também muito perigoso, pois os caminhos dos arquivos acabam sendo avaliados como código shell.
Stéphane Chazelas
Mais uma razão para tentar evitá-lo usando ack / grep corretamente :)
Dennis Kaarsemaker
1
A alternativa de jw013 (dada em um comentário à pergunta) é mais segura a esse respeito. (Só notei que as respostas de Gilles e Stéphane usar a mesma técnica.)
dubiousjim