trouver -exec avec plusieurs commandes

430

J'essaie d'utiliser find -exec avec plusieurs commandes sans succès. Quelqu'un sait-il si des commandes telles que les suivantes sont possibles?

find *.txt -exec echo "$(tail -1 '{}'),$(ls '{}')" \;

Fondamentalement, j'essaie d'imprimer la dernière ligne de chaque fichier txt dans le répertoire en cours et d'imprimer à la fin de la ligne, une virgule suivie du nom de fichier.

Andy
la source
4
superuser.com/questions/236601/…
Ignacio Vazquez-Abrams
1
En ce qui concerne la vérification de la possibilité de la commande, ne l'avez-vous pas essayée sur votre système?
Sriram
1
Depuis la findpage de manuel: There are unavoidable security problems surrounding use of the -exec option; you should use the -execdir option instead.unixhelp.ed.ac.uk/CGI/man-cgi?find
JVE999
@ Le lien JVE999 est rompu, alternative sur ss64.com/bash/find.html
Keith M

Réponses:

659

findaccepte plusieurs -execportions de la commande. Par exemple:

find . -name "*.txt" -exec echo {} \; -exec grep banana {} \;

Notez que dans ce cas, la deuxième commande ne s'exécutera que si la première retourne correctement, comme mentionné par @Caleb. Si vous souhaitez que les deux commandes s'exécutent indépendamment de leur succès ou échec, vous pouvez utiliser cette construction:

find . -name "*.txt" \( -exec echo {} \; -o -exec true \; \) -exec grep banana {} \;
Bricoler
la source
comment grep deux fois? cela échoue: trouver ./* -exec grep -v 'COLD,' {} \; -exec egrep -i "ma_chaine" {} \;
rajeev
51
@rajeev Le deuxième exécutable ne s'exécutera que si le code retour du premier retourne succès, sinon il sera ignoré. Cela devrait probablement être noté dans cette réponse.
Caleb
1
Ce serait la réponse
pylover
1
Notez l'utilisation de -ndans certaines des autres réponses pour supprimer la nouvelle ligne générée par echo, ce qui est pratique si votre deuxième commande ne produit qu'une seule ligne de sortie et que vous souhaitez qu'elles soient plus faciles à lire.
William Turrell
Excellente solution. Fonctionne comme un charme.
Philippe Delteil
98
find . -type d -exec sh -c "echo -n {}; echo -n ' x '; echo {}" \;
Avari
la source
3
Si vous souhaitez exécuter Bash au lieu de Bourne, vous pouvez également utiliser à la ... -exec bash -c ...place de ... -exec sh -c ....
Kenny Evitt du
9
Ne jamais intégrer {}dans le code shell. Voir unix.stackexchange.com/questions/156008/…
Kusalananda
3
+1 @Kusalananda L'injection des noms de fichiers est fragile et peu sûre. Utilisez des paramètres. voir SC2156
pambda
52

L'un des suivants:

find *.txt -exec awk 'END {print $0 "," FILENAME}' {} \;

find *.txt -exec sh -c 'echo "$(tail -n 1 "$1"),$1"' _ {} \;

find *.txt -exec sh -c 'echo "$(sed -n "\$p" "$1"),$1"' _ {} \;
En pause jusqu'à nouvel ordre.
la source
12
À quoi sert le trait de soulignement avant {}?
qed
2
@qed: C'est une valeur jetable qui tient lieu de $0. Essayez ceci avec "foobar" au lieu de "_": find /usr/bin -name find -exec sh -c 'echo "[$0] [$1]"' foobar {} \;- la sortie: "[foobar] [/ usr / bin / find]".
pause jusqu'à nouvel ordre.
1
@XuWang: Oui, je dirais que c'est le cas. Comme vous le savez, $0c'est généralement le nom du programme ( ARGV[0]).
pause jusqu'à nouvel ordre.
3
Il est essentiel, pour cette méthode, que le script transmis sh -csoit entre guillemets simples, et non en double. Sinon, $1c'est dans la mauvaise portée.
Nick
1
@Nick quotes n'a rien à voir avec cela - vous pouvez écrire '$1'avec des guillemets doubles tant que vous échappez à la variable ( "\$1"). Vous pouvez également échapper à d'autres personnages ( "\"").
Camilo Martin
22

Une autre façon est la suivante:

multiple_cmd() { 
    tail -n1 $1; 
    ls $1 
}; 
export -f multiple_cmd; 
find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;

en une seule ligne

multiple_cmd() { tail -1 $1; ls $1 }; export -f multiple_cmd; find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;
  • "multiple_cmd() " - est une fonction
  • "export -f multiple_cmd " - l'exportera pour que tout autre sous-shell puisse le voir
  • " find *.txt -exec bash -c 'multiple_cmd "$0"' {} \;" - trouver qui exécutera la fonction sur votre exemple

De cette façon, multiple_cmd peut être aussi long et complexe que vous le souhaitez.

J'espère que cela t'aides.

al3x2ndru
la source
parfait, juste ce dont j'avais besoin!
Anentropic
ne fonctionne pas pour moi sur OSX
Thomas
@Thomas c'est le cas mais essayez cette doublure, testée en osx. J'ai créé un répertoire appelé 'aaa' avec quelques fichiers / répertoires et un CDd dessus. Ensuite, ~/aaa$ acmd() { echo x \"$1\" x; }; export -f acmd; find . -exec bash -c 'acmd {}' \;
barlop
@barlop, je vais essayer ça; Merci!
Thomas
14

Il existe un moyen plus simple:

find ... | while read -r file; do
    echo "look at my $file, my $file is amazing";
done

Alternativement:

while read -r file; do
    echo "look at my $file, my $file is amazing";
done <<< "$(find ...)"
Camilo Martin
la source
1
les noms de fichiers peuvent contenir des sauts de ligne, c'est pourquoi find a l'argument -print0 et xargs a l'argument -0
abasterfield
@abasterfield J'espère toujours ne jamais trouver ceux dans la nature lol
Camilo Martin
1
ce que je voulais faire était "trouver ... -exec zcat {} | wc -l \;" qui n'a pas fonctionné. Cependant, trouvez ... | pendant la lecture du fichier -r; faire écho "$ file: zcat $file | wc -l"; done fonctionne, alors merci!
Greg Dougherty
Dans le commentaire ci-dessus, j'ai des "tiques inversées" autour de "zcat $ file | wc -l". Malheureusement, SO les transforme en formatage, donc je l'ai ajouté comme réponse réelle avec le bon code visible
Greg Dougherty
1
@GregDougherty Vous pouvez échapper aux backticks `pour faire que vous utilisez des barres obliques inverses comme ceci: \​`(encore, c'est une autre bonne raison d'utiliser à la $()place).
Camilo Martin
8

Je ne sais pas si vous pouvez le faire avec find, mais une autre solution serait de créer un script shell et de l'exécuter avec find.

lastline.sh:

echo $(tail -1 $1),$1

Rendre le script exécutable

chmod +x lastline.sh

Utilisation find:

find . -name "*.txt" -exec ./lastline.sh {} \;
Andrea Spadaccini
la source
7
les backticks sont obsolètes, veuillez encourager l'utilisation de $ (...) qui est mieux lisible, indépendante des polices et facile à imbriquer. Je vous remercie.
utilisateur inconnu
4

La 1ère réponse de Denis est la réponse pour résoudre le problème. Mais en fait, ce n'est plus une recherche avec plusieurs commandes dans un seul exécutable comme le suggère le titre. Pour répondre à un seul exécutable avec plusieurs commandes, nous devrons chercher autre chose à résoudre. Voici un exemple:

Conserver les 10000 dernières lignes de fichiers .log qui ont été modifiés au cours des 7 derniers jours à l'aide de 1 commande exec en utilisant plusieurs références {}

1) voyez ce que la commande fera sur quels fichiers:

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "echo tail -10000 {} \> fictmp; echo cat fictmp \> {} " \;

2) Faites-le: (notez pas plus "\>" mais seulement ">" c'est voulu)

find / -name "*.log" -a -type f -a -mtime -7 -exec sh -c "tail -10000 {} > fictmp; cat fictmp > {} ; rm fictmp" \;

Eric Duruisseau
la source
Mais cela se cassera si l'un des noms de fichiers a un espace, je crois.
Camilo Martin
4

Grâce à Camilo Martin, j'ai pu répondre à une question connexe:

Ce que je voulais faire, c'était

find ... -exec zcat {} | wc -l \;

qui n'a pas fonctionné. cependant,

find ... | while read -r file; do echo "$file: `zcat $file | wc -l`"; done

fonctionne, alors merci!

Greg Dougherty
la source
2

Étendre la réponse de @ Tinker,

Dans mon cas, j'avais besoin de faire un command | command | commandintérieur -execpour imprimer à la fois le nom de fichier et le texte trouvé dans des fichiers contenant un certain texte.

J'ai pu le faire avec:

find . -name config -type f \( -exec  grep "bitbucket" {} \; -a -exec echo {} \;  \) 

le résultat est:

    url = git@bitbucket.org:a/a.git
./a/.git/config
    url = git@bitbucket.org:b/b.git
./b/.git/config
    url = git@bitbucket.org:c/c.git
./c/.git/config
user9869932
la source
1
Vous pouvez également imprimer le nom de fichier et le contenu grep sur une seule ligne en passant /dev/nullcomme deuxième argument à la grepcommande avec un -execparamètre:find . -name config -type f -exec grep "bitbucket" {} /dev/null \;
Bill Feth
1

devrait utiliser xargs :)

find *.txt -type f -exec tail -1 {} \; | xargs -ICONSTANT echo $(pwd),CONSTANT

un autre (travaillant sur osx)

find *.txt -type f -exec echo ,$(PWD) {} + -exec tail -1 {} + | tr ' ' '/'
smapira
la source
3
Cela ignore un cas d'utilisation majeur pour find- les situations où le nombre de fichiers correspondants est trop grand pour une ligne de commande. -execest un moyen de contourner cette limite. La tuyauterie vers un utilitaire manque cet avantage.
Chris Johnson
xargs -nexiste pour choisir le nombre de correspondances par appel. xargs -n 1 foocmds'exécutera foocmd {}pour chaque match.
AndrewF
0

Une find+xargsréponse.

L'exemple ci-dessous trouve tous les .htmlfichiers et crée une copie avec l' .BAKextension ajoutée (par exemple 1.html> 1.html.BAK).

Commande unique avec plusieurs espaces réservés

find . -iname "*.html" -print0 | xargs -0 -I {} cp -- "{}" "{}.BAK"

Commandes multiples avec plusieurs espaces réservés

find . -iname "*.html" -print0 | xargs -0 -I {} echo "cp -- {} {}.BAK ; echo {} >> /tmp/log.txt" | sh

# if you need to do anything bash-specific then pipe to bash instead of sh

Cette commande fonctionnera également avec les fichiers qui commencent par un trait d'union ou contiennent des espaces tels que -my file.htmlgrâce à la citation des paramètres et --après cpquoi signale cpla fin des paramètres et le début des noms de fichiers réels.

-print0 redirige les résultats avec des terminateurs null-byte.


pour xargs, le -I {}paramètre définit {}comme l'espace réservé; vous pouvez utiliser l'espace réservé que vous souhaitez; -0indique que les éléments d'entrée sont séparés par des valeurs nulles.

ccpizza
la source