4 votes

Comment utiliser la commande exec à l'intérieur d'un boucle for après une commande find ?

Ça fait un moment que j'essaie de faire fonctionner ce système. Je veux rechercher une liste de fichiers, puis les copier tous dans un certain chemin.

Si je lance la commande séparément, cela fonctionne comme suit :

find <path> -name 'file1' -exec cp '{}' <path2> \;

Mais je n'ai pas réussi à l'exécuter à l'intérieur d'un boucle for.

#this works
for f in file1 file2 file3...; do find <path> -name "$f" \; done;
#none of these work (this code tries to find and copy the files named file and list from the path)
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp '{}' <path2> \; done;
for f in file1 file2 file3...; do find <path> -name "$f" -exec cp {} <path2> \; done;

J'ai essayé quelques autres trucs qui n'étaient pas susceptibles de fonctionner. La première ligne du code cité se bloque et les autres ne copient rien, même si elles ne se bloquent pas.

Je n'ai pas été en mesure de lancer quoi que ce soit avec exec à l'intérieur d'un boucle for après une recherche et à ce stade, je ne suis pas sûr de ce qu'il faut faire.

J'ai résolu le problème immédiat en effectuant une recherche dans les fichiers et en enregistrant les résultats dans un autre fichier, puis en exécutant une copie à l'intérieur d'un boucle for séparément mais j'aimerais encore savoir comment faire.

7voto

heemayl Points 85741

Deux questions :

  1. for f in some_file n'itère pas sur le contenu de some_file il suffit d'itérer sur ou de prendre la chaîne littérale some_file . Pour surmonter cela, oubliez for pour itérer sur le contenu d'un fichier, utilisez une méthode de bouclage correctement mise en œuvre. while construire.

  2. Les variables ne seront pas développées si elles sont placées entre guillemets simples, '$f' dans ce cas. Pour que votre idée originale fonctionne, utilisez des guillemets.

En combinant ces éléments, en supposant que les noms de fichiers sont séparés par des lignes nouvelles à l'intérieur file_list fichier :

while IFS= read -r f; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done <file_list

Ou si vous savez que les fichiers se trouvent dans le répertoire /path1 et non dans un de ses sous-répertoires, vous pouvez simplement utiliser un tableau pour obtenir les noms de fichiers, et utiliser (GNU) cp directement, en supposant encore une fois que les noms de fichiers sont séparés par des nouvelles lignes à l'intérieur du fichier. file_list :

( 
 IFS=$'\n'
 files=( $(<file_list) )
 cp -t /path2 "${files[@]}"
)

Dans le cas d'un grand nombre de fichiers, il serait préférable d'itérer sur et cp individuellement plutôt que de les déverser dans un tableau :

while IFS= read -r f; do cp -- "$f" /path2; done <file_list

Si vous avez une liste de fichiers comme par exemple file1 file2 file3 ... directement dans le for alors l'utilisation de guillemets doubles suffit :

for f in file1 file2 file3; do 
    find /path1 -name "$f" -exec cp '{}' /path2 \;
done

Maintenant, vous pouvez utiliser cp directement ici aussi si tous les fichiers ne sont pas dans un sous-répertoire :

cp -t /path2 file1 file2 file3

Ou vous pouvez donner des chemins statiques absolus ou relatifs au cas où vous voudriez utiliser cp uniquement pour traiter les fichiers de n'importe quel sous-répertoire.

5voto

Zanna Points 65764

Vous avez précisé que votre liste de fichiers n'est pas un fichier texte, mais une série de noms de fichiers que vous voulez retrouver avec find . Vous avez mentionné que cela fonctionne pour vous :

for f in file1 file2 file3 ... ; do find <path> -name "$f" \; done;

Vous voulez sans doute dire que vous obtenez la liste de fichiers attendue de cette commande.

Vous dites ensuite que cela ne fonctionne pas :

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done

vous voulez probablement dire que les fichiers listés avant ne sont pas copiés sur <path2> . Je ne suis pas sûr de l'erreur que vous obtenez, mais tel qu'il est écrit, je m'attendrais à ce que cette commande se bloque comme ceci :

$for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done
>

en attendant les disparus ; . En supposant que vous ayez corrigé ce problème, je ne vois pas d'autre raison évidente pour laquelle votre commande échouerait définitivement. Vous êtes sur debe être capable de gérer avec

for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; ; done

Cependant, je suggère d'utiliser le -t pour spécifier le répertoire. Je dois remercier David Foerster para ce commentaire qui, je pense, montre la meilleure forme pour un cp o mv invocation en utilisant find . Il refusera également d'écraser les fichiers en double.

for f in file1 file2 file3; do find /path/to/search -name "$f" -exec cp -vt /path/to/destination -- {} + ; done

Notes

  • -v être verbeux - nous dire ce qui est fait
  • -t utiliser le répertoire spécifié comme destination
  • -- n'acceptez pas d'autres options
  • + s'il y a plusieurs correspondances (dans votre cas, cela est peu probable puisque for sera exécuté une fois pour chaque élément de la liste de fichiers, mais il peut y avoir plusieurs fichiers correspondants pour chaque nom), puis construire une liste d'arguments pour cp au lieu d'exécuter cp plusieurs fois
  • ; sépare les commandes dans le Shell. La forme d'un for La boucle est

    for var in things; do command "$var"; done

    S'il ne voit pas le ; avant done , bash attendra ; ou un signal d'interruption.

4voto

David Foerster Points 34353

Bien que les autres réponses soient correctes, je souhaite proposer une approche différente qui ne nécessite pas d'invocations multiples de la fonction find pour analyser la même structure de répertoire à plusieurs reprises. L'idée de base est d'utiliser find a

  1. générer une liste de fichiers qui correspondent aux critères communs,
  2. appliquer un filtre à cette liste, et
  3. effectuer une action, par exemple cp sur les entrées de la liste filtrée.

Mise en œuvre 1

(nécessite Bash pour lire les enregistrements délimités par des octets nuls)

find /some/path -type f -print0 |
while read -rd '' f; do
  case "${f##*/}" in
    file1|file2|file3)
      printf '%s\0' "$f";;
  esac
done |
xargs -r0 -- cp -vt /some/other/path --

Chaque tuyau correspond au début de l'étape suivante des trois étapes décrites ci-dessus.

El case a l'avantage de permettre globbing correspondantes. Vous pouvez également utiliser la fonction expressions conditionnelles :

if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
  printf '%s\0' "$f"
fi

Mise en œuvre 2

Si la liste des noms de fichier à faire correspondre est un peu plus longue et ne peut pas être remplacée par un petit ensemble de globbing ou si la liste des noms de fichiers à faire correspondre n'est pas connue au moment de l'écriture, vous pouvez avoir recours à une tableau qui contient la liste des noms de fichiers d'intérêt :

FILE_NAMES=( "file1" "file2" "file3" ... )

find /some/path -type f -print0 |
while read -rd '' f; do
  for needle in "${FILE_NAMES[@]}"; do
    if [ "${f##*/}" = "$needle" ]; then
      printf '%s\0' "$f"
    fi
  done
done |
xargs -r0 -- cp -vt /some/other/path --

Mise en œuvre 3

En guise de variante, nous pouvons utiliser un tableau associatif qui, espérons-le, a des temps de consultation plus rapides que les tableaux de type "liste" :

declare -A FILE_NAMES=( ["file1"]= ["file2"]= ["file3"]= ... )  # Note the superscripts with []=

find /some/path -type f -print0 |
while read -rd '' f; do
  if [ -v FILE_NAMES["${f##*/}"] ]; then
    printf '%s\0' "$f"
  fi
done |
xargs -r0 -- cp -vt /some/other/path --

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X