Dados esses nomes de arquivo:
$ ls -1
file
file name
otherfile
bash
ele funciona perfeitamente com espaço em branco incorporado:
$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?
No entanto, às vezes talvez eu não queira trabalhar com todos os arquivos, ou mesmo estritamente dentro $PWD
, e é aí que find
entra. O que também lida com espaços em branco nominalmente:
$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name
Eu estou tentando inventar uma versão segura de espaço para script deste scriptlet que pegará a saída find
e a apresentará em select
:
$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file
No entanto, isso explode com espaço em branco nos nomes de arquivos:
$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file 3) name 5) ./directory/file
2) ./file 4) ./directory/file 6) name
Normalmente, eu contornaria isso brincando IFS
. Contudo:
$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
Qual é a solução para isso?
bash
text-processing
whitespace
select
DopeGhoti
fonte
fonte
find
por sua capacidade de corresponder a um nome de arquivo específico, poderá simplesmente usarselect file in **/file*
(após a configuraçãoshopt -s globstar
) embash
4 ou posterior.Respostas:
Se você precisar apenas manipular espaços e guias (novas linhas não incorporadas), poderá usar
mapfile
(ou seu sinônimoreadarray
) para ler em uma matriz, por exemplo,então
Se você fazer necessidade de novas linhas do punho, e sua
bash
versão fornece uma delimitada por nulomapfile
1 , então você pode modificar isso paraIFS= mapfile -t -d '' files < <(find . -type f -print0)
. Caso contrário, monte uma matriz equivalente a partir dafind
saída delimitada por nulo usando umread
loop:1 a
-d
opção foi adicionadamapfile
nabash
versão 4.4 iircfonte
mapfile
é um novo para mim também. Parabéns.while IFS= read
versão funciona no bash v3 (o que é importante para nós que usamos o macOS).find -print0
variante; resmungar por colocá-lo após uma versão conhecida incorreta e descrevê-lo apenas para uso se alguém souber que precisa lidar com novas linhas. Se alguém lida apenas com o inesperado nos locais onde é esperado, nunca lidará com o inesperado.Esta resposta tem soluções para qualquer tipo de arquivo. Com novas linhas ou espaços.
Existem soluções para o bash recente, bem como o bash antigo e até as conchas posix antigas.
A árvore listada abaixo nesta resposta [1] é usada para os testes.
selecionar
É fácil começar
select
a trabalhar com uma matriz:Ou com os parâmetros posicionais:
Portanto, o único problema real é obter a "lista de arquivos" (delimitada corretamente) dentro de uma matriz ou dentro dos Parâmetros Posicionais. Continue lendo.
bater
Não vejo o problema que você relata com o bash. O Bash pode pesquisar dentro de um diretório:
Ou, se você gosta de um loop:
Observe que a sintaxe acima funcionará corretamente com qualquer shell (razoável) (não pelo menos o csh).
O único limite que a sintaxe acima tem é descer para outros diretórios.
Mas o bash poderia fazer isso:
Para selecionar apenas alguns arquivos (como os que terminam em arquivo), substitua o *:
robusto
Quando você coloca um "espaço seguro " no título, vou assumir que o que você quis dizer foi " robusto ".
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 ao conteúdo de um diretório, incluindo nomes de subdiretórios e evitando arquivos de ponto) é:
Se a solução usada for robusta em algum desses itens, remova o teste.
No bash, os subdiretórios podem ser testados de uma só vez com o ** explicado acima.
Existem algumas maneiras de incluir arquivos de ponto, a solução Posix é:
encontrar
Se a localização precisar ser usada por algum motivo, substitua o delimitador por um NUL (0x00).
bash 4.4+
bash 2.05+
POSIXLY
Para criar uma solução POSIX válida em que find não tenha um delimitador NUL e não exista
-d
(nem-a
) para leitura, precisamos de uma abordagem totalmente diferente.Precisamos usar um complexo
-exec
de find com uma chamada para um shell:Ou, se o necessário for um select (select faz parte do bash, não sh):
[1] Esta árvore (as \ 012 são novas linhas):
Pode ser construído com estes dois comandos:
fonte
Você não pode definir uma variável na frente de uma construção em loop, mas pode configurá-la na frente da condição. Aqui está o segmento da página de manual:
(Um loop não é um comando simples .)
Aqui está uma construção comumente usada que demonstra os cenários de falha e sucesso:
Infelizmente, não consigo ver uma maneira de incorporar uma alteração
IFS
naselect
construção enquanto isso afeta o processamento de um associado$(...)
. No entanto, não há nada a impedir deIFS
ser definido fora do loop:e é com essa construção que eu posso ver trabalhos
select
:Ao escrever código defensiva eu recomendo que a cláusula quer ser executado em um subshell, ou
IFS
eSHELLOPTS
salvos e restaurados em torno do bloco:fonte
IFS=$'\n'
é seguro não tem fundamento. Os nomes de arquivos podem perfeitamente conter literais de nova linha.[0-9a-f]{24}
. TB de backups de dados usados para suportar o faturamento de clientes foram perdidos.select
por seu próprio design, é para soluções com script , portanto, ele deve sempre ser projetado para lidar com casos extremos.select
de um shell em que está digitando os comandos a serem executados, mas apenas em um script, em que está respondendo a um prompt fornecido por esse script e onde está esse script executando lógica predefinida (construída sem o conhecimento dos nomes dos arquivos em operação) com base nessa entrada.Posso estar fora da minha jurisdição aqui, mas talvez você possa começar com algo assim, pelo menos não tem nenhum problema com o espaço em branco:
Para evitar possíveis suposições falsas, conforme observado nos comentários, esteja ciente de que o código acima é equivalente a:
fonte
read -d
é uma solução inteligente; obrigado por isso.read -d $'\000'
é exatamente idênticoread -d ''
, mas para pessoas enganosas sobre os recursos do bash (implicando, incorretamente, que ele é capaz de representar NULs literais dentro de strings). Executes1=$'foo\000bar'; s2='foo'
e tente encontrar uma maneira de distinguir entre os dois valores. (Uma versão futura pode normalizar com o comportamento de substituição de comando, tornando o valor armazenado equivalente afoobar
, mas esse não é o caso hoje).