Comment appliquer une commande shell à de nombreux fichiers dans les sous-répertoires imbriqués (et mal échappés)?

j'essaie de faire quelque chose comme ceci:

for file in `find . *.foo`
do
somecommand $file
done

mais la commande ne fonctionne pas car $file est très bizarre. Parce que mon arborescence de répertoires a des noms de fichiers merdiques (y compris des espaces), je dois échapper à la commande find . Mais aucune des évasions évidentes ne semble fonctionner: -ls me donne les fragments de nom de fichier délimités par l'espace -fprint ne fait pas mieux.

j'ai aussi essayé: for file in " trouver . *.foo-ls "; do echo $file; done - but that gives all of the responses from find in one long line.

des indices? Je suis content de pouvoir contourner le problème, mais je suis frustré de ne pas pouvoir le découvrir.

Merci, Alex

(Salut Matt!)

6
demandé sur Tanktalus 2009-04-16 01:32:01

6 réponses

Vous avez beaucoup de réponses qui expliquent bien comment le faire; mais, à cause de l'achèvement je vais répéter et d'ajouter:

xargs n'est utile que pour une utilisation interactive (lorsque vous savez que tous vos noms de fichiers sont simples - pas d'espaces ou de guillemets) ou lorsqu'il est utilisé avec l'option -0 . Sinon, il va tout casser.

find est un outil très utile; mettez l'utiliser pour Piper des noms de fichiers dans xargs (même avec -0 ) est plutôt alambiqué comme find peut le faire tout seul avec soit -exec command {} \; ou -exec command {} + selon ce que vous voulez:

find /path -name 'pattern' -exec somecommand {} \;
find /path -name 'pattern' -exec somecommand {} +

le premier exécute somecommand avec un argument pour chaque fichier récursivement dans /path qui correspond à pattern .

ce dernier exécute somecommand avec autant d'arguments qu'il convient sur la ligne de commande à la fois pour les fichiers récursivement dans /path qui correspondent à pattern .

lequel utiliser dépend de somecommand . S'il peut prendre plusieurs arguments de nom de fichier (comme rm , grep , etc.) alors cette dernière option est plus rapide (puisque vous utilisez somecommand beaucoup moins souvent). Si somecommand ne prend qu'un seul argument alors vous avez besoin de l'ancienne solution. Regardez la page de manuel de somecommand .

plus sur find : http://mywiki.wooledge.org/UsingFind

Dans bash , for est un énoncé qui effectue une itération sur arguments . Si vous faites quelque chose comme ceci:

for foo in "$bar"

vous donnez for un argument d'itérer (notez les guillemets!). Si vous faites quelque chose comme ceci:

for foo in $bar

vous demandez bash de prendre le le contenu de bar et le déchirer partout où il y a des espaces, des onglets ou des lignes (techniquement, quels que soient les caractères dans IFS ) et utiliser les morceaux de cette opération comme arguments pour. ce n'est PAS les noms de fichiers . En supposant que le résultat d'une longue chaîne de caractères qui contient des noms de fichiers séparés partout où il y a des rendements d'espace dans un tas de noms de fichiers est tout simplement erroné. Comme vous l'avez remarqué.

la réponse est: Ne pas utiliser for , c'est évidemment le mauvais outil. Les commandes find ci-dessus supposent Toutes que somecommand est un exécutable dans PATH . Si c'est une instruction bash , vous aurez besoin de cette construction à la place (itère sur la sortie find , comme vous avez essayé, mais en toute sécurité):

while read -r -d ''; do
    somebashstatement "$REPLY"
done < <(find /path -name 'pattern' -print0)

cela utilise une boucle while-read qui lit des parties de la chaîne find jusqu'à ce qu'elle atteigne un byte NULL (ce qui est ce que -print0 utilise pour séparer les noms de fichiers). Comme les octets NULL ne peuvent pas faire partie des noms de fichiers (contrairement aux espaces, aux onglets et aux lignes), c'est une opération sûre.

si vous n'avez pas besoin de somebashstatement pour faire partie de votre script (par ex. il ne change pas l'environnement du script en gardant un compteur ou en définissant une variable ou un tel) alors vous pouvez toujours utiliser find 's -exec pour exécuter votre bash déclaration:

find /path -name 'pattern' -exec bash -c 'somebashstatement ""' -- {} \;
find /path -name 'pattern' -exec bash -c 'for file; do somebashstatement "$file"; done' -- {} +

ici, le -exec exécute une commande bash avec trois arguments ou plus.

  1. Le bash instruction à exécuter.
  2. Un -- . bash va mettre ça dans "1519510920" , vous pouvez mettre ce que vous voulez ici, vraiment.
  3. votre nom de fichier ou vos noms de fichier (selon que vous avez utilisé {} \; ou {} + respectivement). Le nom de fichier(s) fin(s) dans (et , , ... si il n'y a plus d'un, bien sûr).

l'instruction bash dans le premier find commande ici exécute somebashstatement avec le nom de fichier comme argument.

la déclaration bash dans la deuxième commande find exécute ici un for ( ! ) boucle qui itère sur chaque paramètre de position (c'est ce que la syntaxe réduite for - for foo; do - fait) et exécute un somebashstatement avec le nom de fichier comme argument. La différence ici entre la toute première déclaration find que j'ai montrée avec -exec {} + est que nous n'exécutons qu'un seul processus bash pour des lots de noms de fichiers mais encore un somebashstatement pour chaque de ces noms de fichiers.

tout cela est également bien expliqué dans la page UsingFind liée ci-dessus.

11
répondu lhunath 2009-04-16 06:17:23

au lieu de compter sur le shell pour faire ce travail, comptez sur find pour le faire:

find . -name "*.foo" -exec somecommand "{}" \;

alors le nom du fichier sera correctement échappé, et jamais interprété par le shell.

9
répondu Varkhan 2009-04-15 21:34:58
find . -name '*.foo' -print0 | xargs -0 -n 1 somecommand

Il ne gênant si vous avez besoin d'exécuter un certain nombre de commandes shell sur chaque élément.

2
répondu Tanktalus 2009-04-15 21:35:57

xargs est votre ami. Vous voudrez également étudier l'option -0 (zéro) avec elle. find (avec -print0 ) aidera à produire la liste. La page Wikipedia a quelques bons exemples.

une autre raison utile d'utiliser xargs , est que si vous avez beaucoup de fichiers( des dizaines ou plus), xargs les divisera en appels individuels à n'importe quel xargs est alors appelé à exécuter (dans le premier exemple Wikipédia, rm )

1
répondu Alister Bulman 2009-04-15 21:35:40
find . -name '*.foo' -print0 | xargs -0 sh -c 'for F in "${@}"; do ...; done' ""151900920""
1
répondu andrewdotn 2009-04-16 01:49:14

j'ai dû faire quelque chose de similaire il y a quelque temps, renommer des fichiers pour leur permettre de vivre dans des environnements Win32:

#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
  newf=echo "${f}" | sed -e 's/[\/:\*\?#"\|<>]/_/g'
  if [ ${newf} != ${f} ]; then
    echo "${f}" "${newf}"
    mv "${f}" "${newf}"
    f="${newf}"
  fi
  if [[ -d "${f}" ]]; then
    cd "${f}"
    RecurseDirs $(ls -1 ".")
  fi
done
cd ..
}
RecurseDirs .

c'est probablement un peu simpliste, ça n'évite pas les collisions de noms, et je suis sûr que ça pourrait être mieux fait -- mais ça élimine le besoin d'utiliser le nom de base sur les résultats de recherche (dans mon cas) avant d'effectuer mon remplacement sed.

je pourrais vous demander, Que faites-vous aux fichiers trouvés, exactement?

0
répondu dreynold 2009-04-16 21:24:55