web-dev-qa-db-fra.com

Capture de la sortie de la recherche. -print0 dans un tableau bash

En utilisant find . -print0 semble être le seul moyen sûr d'obtenir une liste de fichiers en bash en raison de la possibilité que les noms de fichiers contiennent 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 rediriger 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, évitant ainsi le danger que des sauts de ligne dans les noms de fichiers corrompent le décompte, 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 des valeurs nulles, je pense que la meilleure chose serait de capturer la sortie de find . -print0 dans un tableau bash, comme je l'ai fait dans l'extrait de code Perl ci-dessus, puis continuez 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?

73
Idris

Sans vergogne volé à Greg's BashFAQ :

unset a i
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 la même que le pipeline plus habituel (cmd2 | cmd1) - si les commandes sont des commandes internes de Shell (par exemple while), la version du pipeline les exécute en sous-shell, et toutes les variables qu'ils définissent (par exemple le tableau a) sont perdues à leur sortie. cmd1 < <(cmd2) exécute seulement cmd2 dans un sous-shell, donc le tableau vit après sa construction. Attention: cette forme de redirection n'est disponible qu'en bash, pas même en bash en mode émulation sh; 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 voudrez peut-être faire quelque chose de plus sophistiqué directement dans la boucle) a son entrée redirigée, il ne peut pas utiliser de commandes pouvant lire à partir de stdin. Pour éviter cette limitation, j'ai tendance à utiliser:

unset a i
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 transmet la liste des fichiers via l'unité 3, plutôt que stdin.

99
Gordon Davisson

Peut-être que vous recherchez des xargs:

find . -print0 | xargs -r0 do_something_useful

L'option -L 1 pourrait également vous être utile, ce qui fait que xargs exec do_something_useful avec seulement 1 argument de fichier.

7
Balázs Pozsár

Le problème principal est que le délimiteur NUL (\ 0) est inutile ici, car il n'est pas possible d'affecter à IFS une valeur NUL. Donc, en tant que bons programmeurs, nous veillons à ce que l'entrée de notre programme soit quelque chose qu'il soit 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 for 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 donc qu'une chaîne base64 n'a aucun signe qui cause des problèmes pour bash - bien sûr, un xxd ou quelque chose de similaire peut également faire le travail.

5
zstegi

Encore une autre façon de compter les fichiers:

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

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

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

est:

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

Vous pouvez effectuer le dénombrement 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 ...)

2
Balázs Pozsár

Évitez les 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' 
1
caruso

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

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

Vous pouvez alors par exemple listez 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 bel exemple, et pour plus voir Chapitre 26 dans le Advanced Bash-Scripting Guide .

1
Stephan202

Je suis nouveau mais je crois que c'est une réponse; j'espère que cela aide quelqu'un:

STYLE="$HOME/.fluxbox/styles/"

declare -a array1

LISTING=`find $HOME/.fluxbox/styles/ -print0 -maxdepth 1 -type f`


echo $LISTING
array1=( `echo $LISTING`)
TAR_SOURCE=`echo ${array1[@]}`

#tar czvf ~/FluxieStyles.tgz $TAR_SOURCE
1
pete

Vieille question, mais personne n'a suggéré cette méthode simple, alors j'ai pensé que oui. Accordé si vos noms de fichiers ont un ETX, cela ne résout pas votre problème, mais je soupçonne qu'il sert à n'importe quel scénario du monde réel. Essayer d'utiliser null semble aller à l'encontre des règles de gestion IFS par défaut. Assaisonnez selon 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"
0
Dennis Simpson

Ceci est similaire à la version de Stephan202, mais les fichiers (et répertoires) sont placés dans un tableau d'un seul coup. 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 obtenir un décompte:

echo ${#files[@]}
0
Dennis Williamson

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

Tout d'abord, placez votre 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 une astuce: ^@ 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 l'appel à la commande find peut être évité dans la plupart des cas en utilisant ** syntaxe. Par exemple:

B=( /tmp/** )
0
Jezz