Cette réponse propose des solutions pour tout type de fichiers. Avec des nouvelles lignes ou des espaces.
Il existe des solutions pour les bash récents ainsi que les anciens bash et même les anciens obus posix.
L'arbre ci-dessous dans cette réponse [1] est utilisé pour les tests.
sélectionner
Il est facile de select
travailler soit avec un tableau:
$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done
Ou avec les paramètres positionnels:
$ set -- "$dir"/*
$ select var; do echo "$var"; break; done
Ainsi, le seul vrai problème est d'obtenir la "liste des fichiers" (correctement délimitée) à l'intérieur d'un tableau ou à l'intérieur des paramètres positionnels. Continue de lire.
frapper
Je ne vois pas le problème que vous signalez avec bash. Bash est capable de rechercher dans un répertoire donné:
$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
Ou, si vous aimez une boucle:
$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
Notez que la syntaxe ci-dessus fonctionnera correctement avec n'importe quel shell (raisonnable) (pas csh au moins).
La seule limite de la syntaxe ci-dessus est de descendre dans d'autres répertoires.
Mais bash pourrait faire ça:
$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
Pour sélectionner uniquement certains fichiers (comme ceux qui se terminent par un fichier), remplacez simplement le *:
$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>
robuste
Lorsque vous placez un "espace sûr " dans le titre, je vais supposer que ce que vous vouliez dire était " robuste ".
Le moyen le plus simple d'être robuste sur les espaces (ou les retours à la ligne) est de rejeter le traitement des entrées qui ont des espaces (ou des retours à la ligne). Un moyen très simple de le faire dans le shell est de quitter avec une erreur si un nom de fichier se développe avec un espace. Il y a plusieurs façons de le faire, mais le plus compact (et posix) (mais limité à un contenu de répertoire, y compris les noms suddirectories et en évitant les fichiers dot) est:
$ set -- "$dir"/file* # read the directory
$ a="$(printf '%s' "$@" x)" # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space" # if $a has an space.
$ nl='
' # define a new line in the usual posix way.
$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline" # if $a has a newline.
Si la solution utilisée est robuste dans l'un de ces éléments, supprimez le test.
En bash, les sous-répertoires pouvaient être testés à la fois avec le ** expliqué ci-dessus.
Il existe plusieurs façons d'inclure des fichiers dot, la solution Posix est la suivante:
set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*
trouver
Si find doit être utilisé pour une raison quelconque, remplacez le délimiteur par un NUL (0x00).
bash 4.4+
$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>
bash 2.05+
i=1 # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do
arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"
POSIXLY
Pour créer une solution POSIX valide où find n'a pas de délimiteur NUL et où il n'y a pas -d
(ni -a
) de lecture, nous avons besoin d'une approche entièrement différente.
Nous devons utiliser un complexe -exec
de find avec un appel à un shell:
find "$dir" -type f -exec sh -c '
for f do
echo "<$f>"
done
' sh {} +
Ou, si ce qui est nécessaire est une sélection (la sélection fait partie de bash, pas sh):
$ find "$dir" -type f -exec bash -c '
select f; do echo "<$f>"; break; done ' bash {} +
1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>
[1] Cet arbre (les \ 012 sont des sauts de ligne):
$ tree
.
└── deep
└── inside
└── a
└── dir
├── directory
│ ├── file
│ ├── file name
│ └── file with a \012newline
├── file
├── file name
├── otherfile
├── with a\012newline
└── zz last file
Pourrait être construit avec ces deux commandes:
$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}