Capture de sortie de find. -print0 dans un tableau bash

Utiliser find . -print0 find . -print0 semble être le seul moyen sûr d’obtenir une liste de fichiers en bash en raison de la possibilité de noms de fichiers contenant des espaces, des nouvelles lignes, des guillemets, etc.

Cependant, j’ai du mal à rendre la sortie de find utile dans bash ou avec d’autres utilitaires de ligne de commande. La seule façon dont j’ai réussi à utiliser la sortie est de la redirect vers perl, et de changer l’IFS de perl en null:

 find . -print0 | perl -e '$/="\0"; @files=; print $#files;' 

Cet exemple imprime le nombre de fichiers trouvés, en évitant le risque de nouvelles lignes dans les noms de fichiers endommageant le nombre, comme cela se produirait avec:

 find . | wc -l 

Comme la plupart des programmes en ligne de commande ne prennent pas en charge les entrées délimitées par un caractère nul, je pense que la meilleure chose serait de capturer les résultats de find . -print0 find . -print0 dans un tableau bash, comme je l’ai fait dans l’extrait de perl ci-dessus, puis continue la tâche, quelle qu’elle soit.

Comment puis-je faire ceci?

Cela ne fonctionne pas:

 find . -print0 | ( IFS=$'\0' ; array=( $( cat ) ) ; echo ${#array[@]} ) 

Une question beaucoup plus générale pourrait être: comment puis-je faire des choses utiles avec des listes de fichiers dans bash?

Volé sans vergogne à BashFAQ de Greg :

 unset ai while IFS= read -r -d $'\0' file; do a[i++]="$file" # or however you want to process each file done < <(find /tmp -type f -print0) 

Notez que la construction de redirection utilisée ici ( cmd1 < <(cmd2) ) est similaire à, mais pas tout à fait identique au pipeline plus habituel ( cmd2 | cmd1 ) - si les commandes sont des commandes intégrées (par exemple while ), la version de pipeline les exécute dans des sous-couches, et toutes les variables qu’elles définissent (par exemple le tableau a ) sont perdues lorsqu’elles sortent. cmd1 < <(cmd2) ne s'exécute que dans cmd2 dans un sous-shell, donc le tableau a dépassé sa construction. Attention: cette forme de redirection est uniquement disponible en bash, même pas en mode sh-emulation; vous devez démarrer votre script avec #!/bin/bash .

De plus, comme l’étape de traitement des fichiers (dans ce cas, juste a[i++]="$file" , mais vous pourriez vouloir faire quelque chose de plus directement dans la boucle) a ses entrées redirigées, il ne peut pas utiliser les commandes stdin. Pour éviter cette limitation, j'ai tendance à utiliser:

 unset ai while IFS= read -r -u3 -d $'\0' file; do a[i++]="$file" # or however you want to process each file done 3< <(find /tmp -type f -print0) 

... qui passe la liste de fichiers via l'unité 3 plutôt que stdin.

Vous cherchez peut-être xargs:

 find . -print0 | xargs -r0 do_something_useful 

L’option -L 1 pourrait vous être utile également, ce qui fait que xargs exec do_something_useful ne contient qu’un seul argument de fichier.

Le principal problème est que le délimiteur NUL (\ 0) est inutile ici, car il n’est pas possible d’affecter une valeur NUL à IFS. Donc, en tant que bons programmeurs, nous prenons en compte le fait que l’entrée de notre programme est quelque chose qu’il est capable de gérer.

Nous créons d’abord un petit programme qui fait cette partie pour nous:

 #!/bin/bash printf "%s" "$@" | base64 

… et appelez-le base64str (n’oubliez pas chmod + x)

Deuxièmement, nous pouvons maintenant utiliser une boucle simple et directe:

 for i in `find -type f -exec base64str '{}' \;` do file="`echo -n "$i" | base64 -d`" # do something with file done 

L’astuce est qu’une chaîne de base64 n’a pas de signe qui cause des problèmes à bash – bien sûr, un xxd ou quelque chose de similaire peut aussi faire l’affaire.

Encore une autre façon de compter les fichiers:

 find /DIR -type f -print0 | tr -dc '\0' | wc -c 

Je pense que des solutions plus élégantes existent, mais je vais lancer celle-ci. Cela fonctionnera également pour les noms de fichiers comportant des espaces et / ou des nouvelles lignes:

 i=0; for f in *; do array[$i]="$f" ((i++)) done 

Vous pouvez ensuite par exemple lister les fichiers un par un (dans ce cas dans l’ordre inverse):

 for ((i = $i - 1; i >= 0; i--)); do ls -al "${array[$i]}" done 

Cette page donne un bon exemple, et pour plus d’informations, reportez-vous au chapitre 26 du Guide avancé des scripts Bash .

Vous pouvez faire le décompte en toute sécurité avec ceci:

 find . -exec echo ';' | wc -l 

(Il imprime une nouvelle ligne pour chaque fichier / répertoire trouvé, puis compte les nouvelles lignes imprimées …)

Évitez xargs si vous le pouvez:

 man ruby | less -p 777 IFS=$'\777' #array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' \; 2>/dev/null) ) array=( $(find ~ -maxdepth 1 -type f -exec printf "%s\777" '{}' + 2>/dev/null) ) echo ${#array[@]} printf "%s\n" "${array[@]}" | nl echo "${array[0]}" IFS=$' \t\n' 

Je suis nouveau mais je crois que c’est une réponse; J’espère que ça aide quelqu’un:

 STYLE="$HOME/.streambox/styles/" declare -a array1 LISTING=`find $HOME/.streambox/styles/ -print0 -maxdepth 1 -type f` echo $LISTING array1=( `echo $LISTING`) TAR_SOURCE=`echo ${array1[@]}` #tar czvf ~/FluxieStyles.tgz $TAR_SOURCE 

Ceci est similaire à la version de Stephan202, mais les fichiers (et répertoires) sont placés dans un tableau à la fois. La boucle for ici est juste pour “faire des choses utiles”:

 files=(*) # put files in current directory into an array i=0 for file in "${files[@]}" do echo "File ${i}: ${file}" # do something useful let i++ done 

Pour avoir un compte:

 echo ${#files[@]} 

Ancienne question, mais personne n’a suggéré cette méthode simple, alors j’ai pensé que oui. Si vos noms de fichiers ont un ETX, cela ne résoudra pas votre problème, mais je suppose que cela sert à tout scénario réel. Essayer d’utiliser null semble ne pas respecter les règles de gestion IFS par défaut. Assaisonnez vos goûts avec les options de recherche et la gestion des erreurs.

 savedFS="$IFS" IFS=$'\x3' filenames=(`find wherever -printf %p$'\x3'`) IFS="$savedFS" 

La réponse de Gordon Davisson est géniale pour bash. Cependant, un raccourci utile existe pour les utilisateurs de zsh:

Tout d’abord, placez une chaîne dans une variable:

 A="$(find /tmp -type f -print0)" 

Ensuite, divisez cette variable et stockez-la dans un tableau:

 B=( ${(s/^@/)A} ) 

Il y a un truc: ^@ est le caractère NUL. Pour ce faire, vous devez taper Ctrl + V suivi de Ctrl + @.

Vous pouvez vérifier que chaque entrée de $ B contient la bonne valeur:

 for i in "$B[@]"; echo \"$i\" 

Les lecteurs attentifs peuvent remarquer que la commande d’appel à find peut être évitée dans la plupart des cas en utilisant la syntaxe ** . Par exemple:

 B=( /tmp/** ) 

Depuis Bash 4.4, le mapfile intégré a le commutateur -d (pour spécifier un délimiteur, similaire au commutateur -d de l’instruction read ), et le délimiteur peut être l’octet nul. Par conséquent, une bonne réponse à la question dans le titre

Capture de sortie de find . -print0 find . -print0 dans un tableau bash

est:

 mapfile -d '' ary < <(find . -print0) 

Bash n’a jamais été efficace pour gérer les noms de fichiers (ou n’importe quel texte) car il utilise des espaces comme délimiteur de liste.

Je recommande d’utiliser python avec la bibliothèque sh à la place.