bash - puis-je faire: trouver… -exec ceci && cela?


23

Existe-t-il un moyen de combiner logiquement deux commandes shell appelées avec find-exec ?

Par exemple, pour imprimer tous les fichiers .csv qui contiennent la chaîne foo avec son occurrence, je voudrais faire:

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

mais bash se plaint de "l'argument manquant à '-exec'"


2
Vous pouvez utiliser 2 -execen séquence ou utiliser un seul -exec sh -c 'grep foo "$0" && printf %s\\n "$0"' {} \;.
jw013

Cela m'a fait trébucher à plusieurs reprises: je m'attends toujours à ce que le premier argument transmis à sh(dans ce cas {}) soit $1et $0ressemble à quelque chose sh. Mais en fait, vous avez raison, le premier argument apparaît comme $0. Le fait que le premier argument soit le nom de la commande d'appel n'est qu'une convention, qui n'est pas automatiquement appliquée dans ces cas.
dubiousjim

Peut-être devrait être fusionné avec cette question: unix.stackexchange.com/q/18077/4801
dubiousjim

Réponses:


24

-execest un prédicat qui exécute une commande (pas un shell) et s'évalue à vrai ou faux en fonction du résultat de la commande (état de sortie nul ou non nul).

Alors:

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

serait imprimer le chemin du fichier si greptrouve foo dans le fichier. Au lieu de cela, -printvous pouvez utiliser un autre -execprédicat ou tout autre prédicat

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

Voir aussi les opérateurs !and -ofind pour la négation et ou .

Alternativement, vous pouvez démarrer un shell comme:

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

Ou pour éviter d'avoir à démarrer un shell pour chaque fichier:

find . -iname '*.csv' -exec sh -c '
  for i do
    grep foo "$i" && echo "$i"
  done' sh {} +

10

Le problème auquel vous êtes confronté est que le shell analyse d'abord la ligne de commande et voit deux commandes simples séparées par l' &&opérateur:, find . -iname \*.csv -exec grep foo {}et echo {} \;. Citant &&( find . -iname \*.csv -exec grep foo {} '&&' echo {} \;) court - circuite, mais maintenant la commande exécutée par findquelque chose comme greples arguments foo, wibble.csv, &&, echoet wibble.csv. Vous devez demander findd'exécuter un shell qui interprétera l' &&opérateur:

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

Notez que le premier argument après sh -c SOMECOMMANDest $0non $1.

Vous pouvez enregistrer l'heure de démarrage d'un processus shell pour chaque fichier en regroupant les appels de commandes avec -exec … +. Pour faciliter le traitement, transmettez une valeur fictive $0afin d' "$@"énumérer les noms de fichier.

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

Si la commande shell n'est que de deux programmes séparés par &&, findpeut faire le travail par elle-même: écrire deux -execactions consécutives , et la seconde ne sera exécutée que si la première se termine avec le statut 0.

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

(Je suppose que grepet echosont juste à des fins d'illustration, car ils -exec echopeuvent être remplacés par -printet la sortie résultante n'est de toute façon pas particulièrement utile.)


2
J'ai tendance à éviter d'utiliser "$ 0" pour cela, car il est également utilisé par le shell pour afficher des messages d'erreur. Par exemple, vous pouvez voir un ./some-file: grep: command not foundmessage d'erreur déroutant . -exec find sh -c '... "$1"' sh {} \;n'aurait pas le problème. Il y a une faute de frappe (liée) dans votre deuxième commande find.
Stéphane Chazelas

3

Dans ce cas précis, je ferais:

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

Ou si vous avez un accusé de réception :

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

Pour répondre à votre question réelle, quelque chose comme ça peut fonctionner:

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


3
Cette dernière commande find est non seulement non portable mais également très dangereuse car les chemins de fichiers finissent par être évalués en tant que code shell.
Stéphane Chazelas

Raison de plus pour essayer de l'éviter en utilisant correctement ack / grep :)
Dennis Kaarsemaker

1
L'alternative de jw013 (donnée dans un commentaire à la question) est plus sûre à cet égard. (Je viens de remarquer que les réponses de Gilles et Stéphane utilisent la même technique.)
dubiousjim
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.