bash - ¿puedo hacer: encontrar ... -ejecutar esto y eso?

23

¿Hay alguna manera de combinar lógicamente dos comandos de shell que se invocan con find - exec ?

Por ejemplo, para imprimir todos los archivos .csv que contienen la cadena foo junto con su aparición, me gustaría hacer:

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

pero bash se queja con "argumento perdido para '-exec'"

Marcus Junius Brutus
fuente
2
Podría usar 2 -execen secuencia o usar uno solo -exec sh -c 'grep foo "$0" && printf %s\\n "$0"' {} \;.
jw013
Esto me ha hecho tropezar repetidamente: siempre espero que el primer argumento pasado sh(en este caso {}) sea $1y $0sea ​​algo así sh. Pero, de hecho, tienes razón, el primer argumento aparece como $0. Tener el primer argumento como el nombre del comando de invocación es solo una convención, que no se aplica automáticamente en estos casos.
dubiousjim
Tal vez debería fusionarse con esta pregunta: unix.stackexchange.com/q/18077/4801
dubiousjim

Respuestas:

24

-execes un predicado que ejecuta un comando (no un shell) y se evalúa como verdadero o falso en función del resultado del comando (estado de salida cero o no cero).

Asi que:

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

sería imprimir la ruta del archivo, si greplos hallazgos de Foo en el archivo. En lugar de -printque pueda usar otro -execpredicado o cualquier otro predicado

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

Vea también los operadores !y -ofind para negación y or .

Alternativamente, puede iniciar un shell como:

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

O para evitar tener que iniciar un shell para cada archivo:

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

El problema que enfrenta es que el shell primero analiza la línea de comando y ve dos comandos simples separados por el &&operador: find . -iname \*.csv -exec grep foo {}y echo {} \;. Citando &&( find . -iname \*.csv -exec grep foo {} '&&' echo {} \;) no pasa por eso, pero ahora el comando ejecutado por findalgo así como grepcon los argumentos foo, wibble.csv, &&, echoy wibble.csv. Debe indicarle findque ejecute un shell que interpretará al &&operador:

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

Tenga en cuenta que el primer argumento después sh -c SOMECOMMANDes $0, no $1.

Puede guardar el tiempo de inicio de un proceso de shell para cada archivo agrupando las invocaciones de comando con -exec … +. Para facilitar el procesamiento, pase un valor ficticio $0para "$@"enumerar los nombres de archivo.

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

Si el comando de shell solo tiene dos programas separados &&, findpuede hacer el trabajo por sí mismo: escriba dos -execacciones consecutivas , y la segunda solo se ejecutará si la primera sale con el estado 0.

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

(Supongo que grepy echoson solo para fines ilustrativos, ya que -exec echopueden reemplazarse -printy el resultado resultante no es particularmente útil de todos modos).

Gilles 'SO- deja de ser malvado'
fuente
2
Tiendo a evitar usar "$ 0" para eso, ya que también lo usa el shell para mostrar mensajes de error. Por ejemplo, podría ver un ./some-file: grep: command not foundmensaje de error confuso . -exec find sh -c '... "$1"' sh {} \;No tendría el problema. Hay un error tipográfico (relacionado) en su segundo comando de búsqueda.
Stéphane Chazelas
3

En este caso específico, haría:

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

O si tienes ack :

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

Para responder a su pregunta real, algo como esto puede funcionar:

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

Dennis Kaarsemaker
fuente
3
Este último comando de búsqueda no solo no es portátil, sino que también es muy peligroso, ya que las rutas de archivos terminan siendo evaluadas como código shell.
Stéphane Chazelas
Razón de más para tratar de evitarlo usando ack / grep correctamente :)
Dennis Kaarsemaker
1
La alternativa de jw013 (dada en un comentario a la pregunta) es más segura a este respecto. (Acabo de notar que las respuestas de Gilles y Stephane usan la misma técnica.)
dudoso jim