Maneira compatível com POSIX para trabalhar com uma lista de nomes de arquivos possivelmente com espaço em branco


14

Vi guias de script do Bash sugerindo o uso de matriz para trabalhar com nomes de arquivos que contenham espaço em branco. No entanto, o DashAsBinSh sugere que as matrizes não são portáveis, por isso estou procurando uma maneira compatível com POSIX de trabalhar com listas de nomes de arquivos que podem conter espaços em branco.

Eu estou olhando para modificar o script de exemplo abaixo para que ele echo

foo/target/a.jar
foo/target/b.jar
bar/target/lol whitespace.jar

Aqui está o script

#!/usr/bin/env sh

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"
# this would be produced by a 'ls' command
# We can execute the ls within the script, if it helps

dostuffwith() { echo $1; };

F_LOCATIONS=$INPUT
ALL_FILES=$(for f in $F_LOCATIONS; do echo `basename $f`; done)
ALL_FILES=$(echo "$ALL_FILES" | sort | uniq)

for f in $ALL_FILES
do
    fpath=$(echo "$F_LOCATIONS" | grep -m1 $f)
    dostuffwith $fpath
done

Respostas:


8

Escudos POSIX têm uma matriz: os parâmetros de posição ( $1, $2, etc., colectivamente referidos como "$@").

set -- 'foo/target/a.jar' 'foo/target/b.jar' 'bar/target/b.jar' 'bar/target/lol whitespace.jar'
set -- "$@" '/another/one at the end.jar'

for jar do
  dostuffwith "$jar"
done

Isso é inconveniente porque existe apenas um e destrói qualquer outro uso dos parâmetros posicionais. Os parâmetros posicionais são locais para uma função, que às vezes é uma bênção e às vezes uma maldição.

Se for garantido que seus nomes de arquivo não contenham novas linhas, você poderá usar novas linhas como separador. Ao expandir a variável, primeiro desative o globbing set -fe defina a lista de caracteres de divisão de campo IFSpara conter apenas uma nova linha.

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

set -f; IFS='
'                           # turn off variable value expansion except for splitting at newlines
for jar in $INPUT; do
  set +f; unset IFS
  dostuffwith "$jar"        # restore globbing and field splitting at all whitespace
done
set +f; unset IFS           # do it again in case $INPUT was empty

Com os itens da sua lista separados por novas linhas, você pode usar muitos comandos de processamento de texto de maneira útil, em particular sort.

Lembre-se de sempre colocar aspas duplas em torno das substituições de variáveis, exceto quando você desejar explicitamente que a divisão de campos ocorra (além de globbing, a menos que você tenha desativado).


Boa resposta e explicação. Vou marcar isso como aceito, porque isso faz com que a sort | uniqetapa original funcione conforme o esperado.
Eero Aaltonen

5

Como sua $INPUTvariável usa novas linhas como separadores, vou assumir que seus arquivos não terão novas linhas nos nomes. Como tal, sim, existe uma maneira simples de iterar sobre os arquivos e preservar o espaço em branco.

A idéia é usar o readshell embutido. Normalmente readserá dividido em qualquer espaço em branco e, portanto, os espaços serão divididos. Mas você pode definir IFS=$'\n'e, em vez disso, será dividido apenas em novas linhas. Assim, você pode percorrer cada linha da sua lista.

Aqui está a menor solução que eu poderia encontrar:

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

dostuffwith() {
    echo "$1"
}

echo "$INPUT" | awk -F/ '{if (!seen[$NF]++) print }' | \
while IFS=$'\n' read file; do
  dostuffwith "$file"
done

Basicamente, ele envia "$ INPUT" para o awkqual as deduplicadas são baseadas no nome do arquivo (ele se divide /e depois imprime a linha se o último item não tiver sido visto antes). Então, uma vez que o awk tenha gerado a lista de caminhos de arquivo, usamos while readpara percorrer a lista.


$ Checkbashisms bar.sh possível Bashismo em linha bar.sh 14 (<<< aqui string)
Eero Aaltonen

1
@EeroAaltonen Alterou para não usar o herestring. Observe que, com essa alteração, o whileloop e, portanto, dostuffwithé executado em um subshell. Portanto, quaisquer variáveis ​​ou alterações feitas no shell em execução serão perdidas quando o loop for concluído. A única alternativa é usar um heredoc completo, o que não é tão desagradável, mas achei que seria preferível.
28413 Patrick Patrick

Estou atribuindo pontos baseados mais na legibilidade do que na pequenez. Isso certamente funciona e já +1 para isso.
Eero Aaltonen

IFS="\n"divide em barra invertida e n caracteres. Mas read file, não há divisão. IFS="\n"ainda é útil, pois remove os caracteres em branco do $ IFS que, de outra forma, teriam sido removidos no início e no final da entrada. Para ler uma linha, a sintaxe canônica é ( IFS= read -r lineembora nada contenha espaços em branco) também funcionará. IFS=anything read -r line
Stéphane Chazelas

oops. Não sei como eu consegui isso. Fixo.
Patrick
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.