Esta resposta tem soluções para qualquer tipo de arquivos. Com novas linhas ou espaços.
Existem soluções para o bash recente, bem como para o bash antigo e até mesmo para o velho posix.
A árvore listada abaixo nesta resposta [1] é usada para os testes.
selecione
É fácil obter select
para trabalhar com uma matriz:
$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done
Ou com os parâmetros posicionais:
$ set -- "$dir"/*
$ select var; do echo "$var"; break; done
Assim, o único problema real é obter a "lista de arquivos" (delimitada corretamente) dentro de uma matriz ou dentro dos Parâmetros posicionais. Continue lendo.
bash
Eu não vejo o problema que você relata com o bash.
O Bash pode pesquisar dentro de um determinado diretório:
$ 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, se você gosta de um 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>
Note que a sintaxe acima funcionará corretamente com qualquer shell (razoável) (não pelo menos csh).
O único limite que a sintaxe acima tem é descer em outros diretórios.
Mas o bash poderia fazer isso:
$ 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>
Para selecionar apenas alguns arquivos (como aqueles que terminam no arquivo), basta substituir o *:
$ 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 você coloca um "espaço- seguro " no título, vou assumir que o que você quis dizer foi " robust ".
A maneira mais simples de ser robusto sobre espaços (ou novas linhas) é rejeitar o processamento de entrada que possui espaços (ou novas linhas). Uma maneira muito simples de fazer isso no shell é sair com um erro se algum nome de arquivo se expandir com um espaço. Existem várias maneiras de fazer isso, mas o mais compacto (e posix) (mas limitado a um conteúdo de diretório, incluindo nomes de diretórios e evitando arquivos de pontos) é:
$ 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 a solução usada for robusta em qualquer um desses itens, remova o teste.
No bash, os subdiretórios podem ser testados imediatamente com o ** explicado acima.
Existem algumas maneiras de incluir arquivos de ponto, a solução Posix é:
set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*
encontrar
Se for preciso encontrar algum motivo, substitua o delimitador por um 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
Para fazer uma solução POSIX válida em que o find não tenha um delimitador NUL e não exista -d
(nem -a
) para leitura, precisamos de uma abordagem completamente diferente.
Precisamos usar um -exec
complexo para encontrar com uma chamada para um shell:
find "$dir" -type f -exec sh -c '
for f do
echo "<$f>"
done
' sh {} +
Ou, se o que é necessário é um select (select faz parte do bash, não 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] Esta árvore (o \ 012 são novas linhas):
$ tree
.
└── deep
└── inside
└── a
└── dir
├── directory
│ ├── file
│ ├── file name
│ └── file with a 2newline
├── file
├── file name
├── otherfile
├── with a2newline
└── zz last file
Poderia ser criado com estes dois comandos:
$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}