J'essaie de faire fonctionner ça depuis un moment maintenant. Je souhaite rechercher une liste de fichiers, puis les copier tous dans un certain chemin.
Si j'exécute la commande séparément, cela fonctionne comme suit:
find <path> -name 'file1' -exec cp '{}' <path2> \;
Mais j'ai été incapable de l'exécuter dans une 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 ne fonctionneraient probablement pas. La première ligne de la citation de code reste bloquée et les autres ne copient rien même s'ils ne sont pas bloqués.
Après une recherche, je n'ai rien pu faire avec exec dans une boucle for et à ce stade, je ne sais pas trop quoi faire.
J'ai résolu le problème immédiat en recherchant les fichiers, en enregistrant les résultats dans un autre fichier, puis en exécutant une copie dans une boucle for séparément, mais j'aimerais quand même savoir comment procéder.
Bien que les autres réponses soient correctes, je souhaite proposer une approche différente ne nécessitant pas plusieurs appels de find
pour analyser la même structure de répertoires à plusieurs reprises. L'idée de base est d'utiliser find
pour
cp
, sur les entrées de la liste filtrée.(nécessite que Bash lise des 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 la prochaine étape des trois étapes décrites ci-dessus.
L'instruction case
a l'avantage d'autoriser les correspondances globbing . Sinon, vous pouvez utiliser Bash expressions conditionnelles :
if [[ "${f##*/}" == file1 || "${f##*/}" == file2 || "${f##*/}" == file3 ]]; then
printf '%s\0' "$f"
fi
Si la liste des noms de fichiers à comparer est un peu plus longue et ne peut pas être remplacée par un petit ensemble de modèles globbing , ou si la liste des noms de fichiers à associer n'est pas connue au moment de l'écriture, vous peut avoir recours à un tablea 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 --
En variante, nous pouvons utiliser un tableau associatif qui, espérons-le, a des temps de recherche plus rapides que les tableaux "list":
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 --
Deux problèmes:
for f in some_file
n'itère pas le contenu de some_file
, il suffit d'itérer ou de prendre la chaîne littérale some_file
. Pour surmonter cela, oubliez la boucle for
pour parcourir le contenu du fichier, utilisez une construction while
correctement implémentée.
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 doubles.
En les assemblant, en supposant que les noms de fichiers sont séparés par une nouvelle ligne dans le fichier file_list
:
while IFS= read -r f; do
find /path1 -name "$f" -exec cp '{}' /path2 \;
done <file_list
Ou si vous savez que les fichiers se trouveront dans le répertoire /path1
, et non dans un sous-répertoire de celui-ci, vous pouvez simplement utiliser un tableau pour obtenir les noms de fichiers et utiliser directement (GNU) cp
, en supposant que newline noms de fichiers séparés à l'intérieur de file_list
:
(
IFS=$'\n'
files=( $(<file_list) )
cp -t /path2 "${files[@]}"
)
En cas de très grand nombre de fichiers, mieux vaut effectuer une itération individuelle et cp
plutôt que de les placer 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 la construction for
, alors simplement utiliser des guillemets doubles ferait:
for f in file1 file2 file3; do
find /path1 -name "$f" -exec cp '{}' /path2 \;
done
Maintenant, vous pouvez utiliser cp
directement ici si tous les fichiers ne sont dans aucun sous-répertoire:
cp -t /path2 file1 file2 file3
Vous pouvez également donner des chemins absolus ou relatifs statiques au cas où vous voudriez utiliser cp
uniquement pour traiter les fichiers de tous les sous-répertoires.
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 souhaitez rechercher avec find
. Vous avez mentionné que cela fonctionne pour vous:
for f in file1 file2 file3 ... ; do find <path> -name "$f" \; done;
vous voulez probablement dire que vous obtenez la liste de fichiers attendue à partir de cette commande.
Vous dites alors 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 précédemment ne sont pas copiés dans <path2>
. Je ne suis pas sûr de l'erreur que vous obtenez, mais comme il est écrit, je m'attendrais à ce que la commande soit bloquée comme ceci:
$for f in file1 file2 file3 ... ; do find <path> -name "$f" -exec cp '{}' <path2> \; done
>
en attente du ;
manquant. En supposant que vous corrigiez ce problème, je ne peux penser à aucune autre raison évidente pour laquelle votre commande échouerait définitivement. Vous devriez ê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 l'indicateur -t
pour spécifier le répertoire. Je dois remercier David Foerster pour ce commentaire qui, à mon avis, présente la meilleure forme pour une invocation cp
ou mv
à l'aide de 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
-v
be verbose - dites-nous ce qui se fait-t
utilise le répertoire spécifié comme destination--
n'accepte aucune autre option+
s'il y a plusieurs correspondances (dans votre cas, c'est peu probable car for
ne sera exécuté qu'une fois pour chaque élément de la liste des fichiers, mais il peut y avoir plusieurs fichiers correspondants pour chaque nom), puis construisez un argument liste pour cp
au lieu d'exécuter cp
plusieurs fois;
sépare les commandes dans le shell. La forme d'une boucle for
est
for var in things; do command "$var"; done
S'il ne voit pas le ;
avant done
, bash attendra ;
ou un signal d'interruption.