Listar elementos com espaços no zsh

10

Eu estudei o script zsh por duas horas neste momento e bati em uma parede. Quero passar por uma lista de arquivos que podem ter espaços neles. Estou aberto a abordagens completamente diferentes do exemplo a seguir, desde que sejam zsh, já que zsh é o que estou estudando, não a tarefa que estou tentando criar.

files=(`ls`)
for f in $files; do
    print $f
done

Obviamente, não estou apenas recriando ls, apenas quero $fcapturar o nome do arquivo inteiro a cada iteração do loop.

Felix
fonte

Respostas:

12

Primeiro, suponho que o uso de lsé apenas um exemplo. Você não pode analisar a saída de lsqualquer shell, porque é ambígua. Leia Por que você não deve analisar a saída de ls (1) se isso é novidade para você. Em qualquer shell, para obter uma lista de arquivos, use caracteres curinga, por exemplo files=(*).

No zsh, como em outros shells, o resultado da substituição de comandos é dividido em palavras com caracteres de espaço em branco (mais precisamente, de acordo com o valor de IFS). (Diferentemente de outros shells, o resultado da substituição do comando não está sujeito a globbing no zsh.) Portanto, se a saída do lscomando for

hello world
wibble

em seguida, files=($(ls))define a filesmatriz para conter 3 elementos: hello, worlde wibble.

Se a substituição do comando estiver entre aspas duplas, nenhuma divisão será realizada. Você pode executar uma divisão personalizada com sinalizadores de expansão de parâmetro . Use o @sinalizador para indicar que o resultado da divisão será uma matriz (estranhamente, é necessário manter a expansão entre aspas duplas, ou seja "${(@)…}", mesmo que a cadeia de aspas duplas se expanda para várias palavras). Para dividir, use a sbandeira, por exemplo, "${(@s:,:)…}"para dividir por vírgulas; a fbandeira se divide apenas em novas linhas.

files=("${(@f)$(ls)}")

Observe que a maneira correta de iterar sobre uma matriz em geral é for f in $files[@], como $filesretira elementos vazios (aqui, isso não importa porque os elementos não estarão vazios).

print $finterpreta $fcomo uma opção se começar com -ae expandir barras invertidas $f. Use print -r -- $fou print -rn -- $fse você não quiser adicionar uma nova linha após a sequência.

Gilles 'SO- parar de ser mau'
fonte
Obrigado. Sim, foi apenas um exemplo. Não havia um objetivo específico em mente no momento, só quero saber como citar ou escapar dos elementos da lista de qualquer comando que possa ter resultados individuais com espaços em branco.
Felix
Uma coisa que acho confusa é por que a expressão externa () em seus arquivos = ("$ {(@ f) $ (ls)}") é necessária com o @f? E apenas brincar com (f) parece funcionar bem desde que () apareça do lado de fora.
Koobz
@Koobz 1. O externo (…)é necessário para que filesseja uma matriz e não uma string (que conteria a concatenação das linhas do comando, desfazendo a divisão feita por (f)). 2. Com foo=$'one\n\nthree', contrast print -rl $ {(f) foo} `e print "${(@f)foo}". As aspas duplas são necessárias para manter as linhas vazias, das quais você não obterá, lsmas pode acontecer com outros comandos.
Gilles 'SO- stop be evil'
Muito apreciado. Existe alguma rima ou razão para essa loucura? Mesmo lá, o que é confuso é por que o outer () não divide novamente a string em palavras individuais novamente se o valor intermediário é minha string concatenada. Parece interpolação de string em ruby ​​ou php, por exemplo.
Koobz
@Koobz O motivo do tratamento especial de palavras vazias é a compatibilidade histórica com versões antigas do zsh há muito esquecidas. A razão para não se dividir automaticamente é que é um comportamento surpreendente e muitas vezes não desejável. A divisão automática é um comportamento do shell Bourne que o zsh não emula, a menos que você o solicite.
Gilles 'SO- deixe de ser mau'
2

No zsh, você pode usar a expansão do shell, que não executa a divisão de palavras por padrão. Experimentar

for f in /path/to/files/*; do
    print ${f}
done
u-punkt
fonte