Questa risposta ha soluzioni per qualsiasi tipo di file. Con newline o spazi.
Ci sono soluzioni per bash recenti, nonché bash antichi e persino conchiglie posix vecchie.
L'albero elencato di seguito in questa risposta [1] viene utilizzato per i test.
Selezionare
È facile arrivare select
a lavorare con un array:
$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done
O con i parametri posizionali:
$ set -- "$dir"/*
$ select var; do echo "$var"; break; done
Quindi, l'unico vero problema è ottenere la "lista di file" (correttamente delimitata) all'interno di un array o all'interno dei parametri posizionali. Continua a leggere.
bash
Non vedo il problema segnalato con bash. Bash è in grado di cercare all'interno di una determinata directory:
$ 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>
Oppure, se ti piace un loop:
$ 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>
Nota che la sintassi sopra funzionerà correttamente con qualsiasi shell (ragionevole) (non almeno csh).
L'unico limite che ha la sintassi sopra è di scendere in altre directory.
Ma bash potrebbe farlo:
$ 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>
Per selezionare solo alcuni file (come quelli che finiscono nel file) è sufficiente sostituire *:
$ 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>
robusto
Quando si inserisce uno "spazio- sicuro " nel titolo, suppongo che ciò che intendevi fosse " robusto ".
Il modo più semplice per essere robusti riguardo agli spazi (o newline) è rifiutare l'elaborazione di input che ha spazi (o newline). Un modo molto semplice per farlo nella shell è uscire con un errore se un nome di file si espande con uno spazio. Esistono diversi modi per farlo, ma il più compatto (e posix) (ma limitato ai contenuti di una directory, inclusi i nomi delle suddirectory ed evitando i file di punti) è:
$ 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.
Se la soluzione utilizzata è robusta in uno di questi elementi, rimuovere il test.
In bash, le sottodirectory potevano essere testate contemporaneamente con il ** spiegato sopra.
Esistono un paio di modi per includere file dot, la soluzione Posix è:
set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*
trova
Se find deve essere utilizzato per qualche motivo, sostituire il delimitatore con 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
Per creare una soluzione POSIX valida in cui find non ha un delimitatore NUL e non esiste -d
(né -a
) per la lettura, è necessario un approccio completamente diverso.
Dobbiamo usare un complesso -exec
da find con una chiamata a una shell:
find "$dir" -type f -exec sh -c '
for f do
echo "<$f>"
done
' sh {} +
Oppure, se ciò che serve è una selezione (select fa parte di bash, non 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] Questo albero (i \ 012 sono newline):
$ tree
.
└── deep
└── inside
└── a
└── dir
├── directory
│ ├── file
│ ├── file name
│ └── file with a \012newline
├── file
├── file name
├── otherfile
├── with a\012newline
└── zz last file
Potrebbe essere costruito con questi due comandi:
$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}