x=$(find . -name "*.txt")
echo $x
si j'exécute le morceau de code ci-dessus dans le shell Bash, ce que j'obtiens est une chaîne contenant plusieurs noms de fichiers séparés par des blancs, pas une liste.
Bien sûr, je peux les séparer davantage en blanc pour obtenir une liste, mais je suis sûr qu'il existe une meilleure façon de le faire.
Alors, quelle est la meilleure façon de parcourir les résultats d'une find
commande?
x=( $(find . -name "*.txt") ); echo "${x[@]}"
Ensuite, vous pouvez parcourirfor item in "${x[@]}"; { echo "$item"; }
Réponses:
TL; DR: Si vous êtes juste ici pour la réponse la plus correcte, vous voulez probablement ma préférence personnelle
find . -name '*.txt' -exec process {} \;
(voir le bas de cet article). Si vous avez le temps, lisez le reste pour voir plusieurs façons différentes et les problèmes avec la plupart d'entre elles.La réponse complète:
La meilleure façon dépend de ce que vous voulez faire, mais voici quelques options. Tant qu'aucun fichier ou dossier dans la sous-arborescence n'a d'espace dans son nom, vous pouvez simplement parcourir les fichiers:
Un peu mieux, supprimez la variable temporaire
x
:Il est beaucoup mieux glob quand vous le pouvez. Sécuritaire pour les espaces blancs, pour les fichiers du répertoire courant:
En activant l'
globstar
option, vous pouvez glober tous les fichiers correspondants dans ce répertoire et tous les sous-répertoires:Dans certains cas, par exemple si les noms de fichiers sont déjà dans un fichier, vous devrez peut-être utiliser
read
:read
peut être utilisé en toute sécurité en combinaison avecfind
en définissant le délimiteur de manière appropriée:Pour des recherches plus complexes, vous voudrez probablement utiliser
find
, soit avec son-exec
option, soit avec-print0 | xargs -0
:find
peut également se placer dans le répertoire de chaque fichier avant d'exécuter une commande en utilisant-execdir
au lieu de-exec
, et peut être rendu interactif (demander avant d'exécuter la commande pour chaque fichier) en utilisant-ok
au lieu de-exec
(ou-okdir
au lieu de-execdir
).*: Techniquement, les deux
find
etxargs
(par défaut) exécuteront la commande avec autant d'arguments qu'ils peuvent tenir sur la ligne de commande, autant de fois qu'il faut pour parcourir tous les fichiers. En pratique, à moins que vous n'ayez un très grand nombre de fichiers, cela n'aura pas d'importance, et si vous dépassez la longueur mais que vous en avez tous besoin sur la même ligne de commande,vous êtes SOLtrouver une manière différente.la source
done < filename
et le suivant avec le tuyau, le stdin ne peut plus être utilisé (→ plus de trucs interactifs à l'intérieur de la boucle), mais dans les cas où cela est nécessaire, on peut utiliser à la3<
place de<
et ajouter<&3
ou-u3
à laread
partie, en utilisant essentiellement un descripteur de fichier distinct. De plus, je pense queread -d ''
c'est la même choseread -d $'\0'
mais je ne trouve aucune documentation officielle à ce sujet pour le moment.-exec process {} \;
et je suppose que c'est une toute autre question - qu'est-ce que cela signifie et comment puis-je la manipuler? Où est un bon Q / A ou doc. dessus?man find
). Dans ce cas,-exec
indiquefind
d'exécuter la commande suivante, terminée par;
(ou+
), dans laquelle{}
sera remplacé par le nom du fichier qu'il traite (ou, s'il+
est utilisé, tous les fichiers qui ont atteint cette condition).-d ''
est meilleur que-d $'\0'
. Ce dernier est non seulement plus long, mais suggère également que vous pouvez passer des arguments contenant des octets nuls, mais vous ne le pouvez pas. Le premier octet nul marque la fin de la chaîne. En bash$'a\0bc'
est le même quea
et$'\0'
est identique$'\0abc'
ou juste la chaîne vide''
.help read
déclare que " Le premier caractère de délimitation est utilisé pour terminer l'entrée ", donc l'utilisation''
comme délimiteur est un peu un hack. Le premier caractère de la chaîne vide est l'octet nul qui marque toujours la fin de la chaîne (même si vous ne l'écrivez pas explicitement).Quoi que vous fassiez, n'utilisez pas de
for
boucle :Trois raisons:
find
exécution doit se terminer.for
boucle retourne 40 Ko de texte. Ces 8 derniers Ko seront supprimés de votrefor
boucle et vous ne le saurez jamais.Utilisez toujours une
while read
construction:La boucle s'exécutera pendant l'exécution de la
find
commande. De plus, cette commande fonctionnera même si un nom de fichier est retourné avec des espaces. Et, vous ne dépasserez pas votre mémoire tampon de ligne de commande.Le
-print0
va utiliser le NULL comme séparateur de fichier au lieu d'une nouvelle ligne et le-d $'\0'
va utiliser NULL comme séparateur lors de la lecture.la source
-exec
plutôt les trouvailles .-exec
est le plus sûr car il n'utilise pas du tout le shell. Cependant, NL dans les noms de fichiers est assez rare. Les espaces dans les noms de fichiers sont assez courants. Le point principal n'est pas d'utiliser unefor
boucle recommandée par de nombreuses affiches.for file $(find)
cause des problèmes associés à cela.-r
option pourread
:-r raw input - disables interpretion of backslash escapes and line-continuation in the read data
Remarque: cette méthode et la (seconde) méthode montrées par bmargulies sont sûres à utiliser avec des espaces blancs dans les noms de fichiers / dossiers.
Afin de couvrir également le cas - quelque peu exotique - de nouvelles lignes dans les noms de fichiers / dossiers, vous devrez recourir au
-exec
prédicatfind
comme ceci:L'
{}
est l'espace réservé pour l'élément trouvé et l'\;
est utilisé pour mettre fin à la-exec
prédicat.Et par souci d'exhaustivité, permettez-moi d'ajouter une autre variante - vous devez aimer les façons * nix pour leur polyvalence:
Cela séparerait les éléments imprimés avec un
\0
caractère qui n'est autorisé dans aucun des systèmes de fichiers dans les noms de fichiers ou de dossiers, à ma connaissance, et devrait donc couvrir toutes les bases.xargs
les ramasse un par un puis ...la source
find -print0
et cexargs -0
sont à la fois des extensions GNU et non des arguments portables (POSIX). Incroyablement utile sur les systèmes qui en ont, cependant!read -r
corrigeraient) ou les noms de fichiers se terminant par des espaces (quiIFS= read
corrigeraient). D'où BashFAQ # 1 suggérantwhile IFS= read -r filename; do ...
exit
ne fonctionnera pas comme prévu et les variables définies dans le corps de la boucle ne seront pas disponibles après la boucle.Les noms de fichiers peuvent inclure des espaces et même des caractères de contrôle. Les espaces sont (par défaut) des délimiteurs pour l'expansion du shell dans bash et à la suite de cela
x=$(find . -name "*.txt")
de la question n'est pas recommandé du tout. Si find obtient un nom de fichier avec des espaces, par exemple,"the file.txt"
vous obtiendrez 2 chaînes séparées pour le traitement, si vous traitezx
en boucle. Vous pouvez améliorer cela en changeant le délimiteur (IFS
variable bash ) par exemple en\r\n
, mais les noms de fichiers peuvent inclure des caractères de contrôle - ce n'est donc pas une méthode (complètement) sûre.De mon point de vue, il existe 2 modèles recommandés (et sûrs) pour le traitement des fichiers:
1. Utilisez pour l'extension de boucle et de nom de fichier:
2. Utilisez la recherche par lecture et la substitution de processus
Remarques
sur le motif 1:
nullglob
peut être utilisée pour éviter cette ligne supplémentaire.failglob
option shell est définie et qu'aucune correspondance n'est trouvée, un message d'erreur est imprimé et la commande n'est pas exécutée." (extrait du manuel Bash ci-dessus)globstar
: "S'il est défini, le modèle '**' utilisé dans un contexte d'extension de nom de fichier correspondra à tous les fichiers et à zéro ou plusieurs répertoires et sous-répertoires. Si le modèle est suivi d'un '/', seuls les répertoires et sous-répertoires correspondent." voir Bash Manual, Shopt Builtinextglob
,nocaseglob
,dotglob
& variable shellGLOBIGNORE
sur le motif 2:
les noms de fichiers peuvent contenir des blancs, des tabulations, des espaces, des retours à la ligne, ... pour traiter les noms de fichiers de manière sûre,
find
avec-print0
est utilisé: le nom de fichier est imprimé avec tous les caractères de contrôle et terminé avec NUL. voir aussi la page de manuel de Gnu Findutils, la gestion non sécurisée des noms de fichiers , la gestion sûre des noms de fichiers , les caractères inhabituels dans les noms de fichiers . Voir David A. Wheeler ci-dessous pour une discussion détaillée de ce sujet.Il existe certains modèles possibles pour traiter les résultats de la recherche dans une boucle while. D'autres (kevin, David W.) ont montré comment faire cela en utilisant des tuyaux:
Lorsque vous essayez ce morceau de code, vous verrez qu'il ne fonctionne pas:files_found
est toujours "vrai" et le code fera toujours écho "aucun fichier trouvé". La raison est: chaque commande d'un pipeline est exécutée dans un sous-shell séparé, donc la variable modifiée à l'intérieur de la boucle (sous-shell séparé) ne change pas la variable dans le script shell principal. C'est pourquoi je recommande d'utiliser la substitution de processus comme modèle "meilleur", plus utile et plus général.Voir Je place des variables dans une boucle qui est dans un pipeline. Pourquoi disparaissent-ils ... (de la FAQ de Greg's Bash) pour une discussion détaillée sur ce sujet.
Références et sources supplémentaires:
Manuel Gnu Bash, Correspondance de motifs
Noms de fichiers et chemins d'accès dans Shell: comment le faire correctement, David A. Wheeler
Pourquoi vous ne lisez pas les lignes avec "pour", le wiki de Greg
Pourquoi vous ne devriez pas analyser la sortie de ls (1), le wiki de Greg
Manuel Gnu Bash, Substitution de processus
la source
(Mis à jour pour inclure l'amélioration de la vitesse de l'excellent @ Socowi)
Avec tous ceux
$SHELL
qui le supportent (dash / zsh / bash ...):Terminé.
Réponse originale (plus courte mais plus lente):
la source
\;
vous pouvez utiliser+
pour passer autant de fichiers que possible à un seulexec
. Utilisez ensuite"$@"
à l'intérieur du script shell pour traiter tous ces paramètres.$@
omet car c'est généralement le nom du script. Nous avons juste besoin d'ajouterdummy
entre'
et{}
il peut prendre la place du nom de script, assurant tous les matches sont traités par la boucle.OTHERVAR=foo find . -na.....
devrait vous permettre d'accéder à$OTHERVAR
partir de ce shell nouvellement créé.la source
for x in $(find ...)
se cassera pour tout nom de fichier avec des espaces en elle. Même chose àfind ... | xargs
moins que vous n'utilisiez-print0
et-0
find . -name "*.txt -exec process_one {} ";"
plutôt. Pourquoi devrions-nous utiliser xargs pour collecter des résultats, nous l'avons déjà?process_one
est. S'il s'agit d'un espace réservé pour une commande réelle , assurez-vous que cela fonctionnerait (si vous corrigez une faute de frappe et ajoutez des guillemets de fermeture après"*.txt
). Mais s'ilprocess_one
s'agit d'une fonction définie par l'utilisateur, votre code ne fonctionnera pas.Vous pouvez stocker votre
find
sortie dans un tableau si vous souhaitez utiliser la sortie ultérieurement comme:Maintenant, pour imprimer chaque élément dans une nouvelle ligne, vous pouvez soit utiliser l'
for
itération en boucle pour tous les éléments du tableau, soit utiliser l'instruction printf.ou
Vous pouvez aussi utiliser:
Cela affichera chaque nom de fichier en nouvelle ligne
Pour imprimer uniquement la
find
sortie sous forme de liste, vous pouvez utiliser l'une des méthodes suivantes:ou
Cela supprimera les messages d'erreur et ne donnera que le nom de fichier en sortie dans la nouvelle ligne.
Si vous souhaitez faire quelque chose avec les noms de fichiers, le stocker dans un tableau est bon, sinon il n'est pas nécessaire de consommer cet espace et vous pouvez directement imprimer la sortie à partir de
find
.la source
Si vous pouvez supposer que les noms de fichiers ne contiennent pas de retours à la ligne, vous pouvez lire la sortie de
find
dans un tableau Bash à l'aide de la commande suivante:Remarque:
-t
provoque lareadarray
suppression des sauts de ligne.readarray
est dans un tube, d'où la substitution de processus.readarray
est disponible depuis Bash 4.Bash 4.4 et versions ultérieures prennent également en charge le
-d
paramètre de spécification du délimiteur. L'utilisation du caractère nul, au lieu de la nouvelle ligne, pour délimiter les noms de fichiers fonctionne également dans les rares cas où les noms de fichiers contiennent des nouvelles lignes:readarray
peut également être invoqué commemapfile
avec les mêmes options.Référence: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
la source
exit
lors du bouclage sur les résultatsreadarray -d '' x < <(find . -name '*.txt' -print0)
J'aime utiliser find qui est d'abord affecté à une variable et IFS est passé à une nouvelle ligne comme suit:
Juste au cas où vous voudriez répéter plus d'actions sur le même ensemble de DONNÉES et trouver est très lent sur votre serveur (utilisation I / 0 élevée)
la source
Vous pouvez mettre les noms de fichiers renvoyés par
find
dans un tableau comme celui-ci:Maintenant, vous pouvez simplement parcourir le tableau pour accéder aux éléments individuels et faire ce que vous voulez avec eux.
Remarque: Il est sûr pour les espaces blancs.
la source
mapfile -t -d '' array < <(find ...)
. Le réglageIFS
n'est pas nécessaire pourmapfile
.basé sur d'autres réponses et commentaires de @phk, en utilisant fd # 3:
(qui permet toujours d'utiliser stdin à l'intérieur de la boucle)
la source
find <path> -xdev -type f -name *.txt -exec ls -l {} \;
Cela répertorie les fichiers et donne des détails sur les attributs.
la source
Et si vous utilisiez grep au lieu de trouver?
Vous pouvez maintenant lire ce fichier et les noms de fichiers se présentent sous la forme d'une liste.
la source