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

13

Eu vi guias de script Bash sugerindo o uso de matriz para trabalhar com nomes de arquivos contendo espaços em branco. No entanto, o DashAsBinSh sugere que as matrizes não são portáteis, então estou procurando uma maneira compatível com POSIX de trabalhar com listas de nomes de arquivos que possam conter espaços em branco.

Eu estou procurando modificar o script de exemplo abaixo para que seja 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
    
por Eero Aaltonen 28.11.2013 / 10:12

2 respostas

7

Cascas POSIX têm um array: os parâmetros posicionais ( $1 , $2 , etc., coletivamente referenciados 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, o que às vezes é uma bênção e, às vezes, uma maldição.

Se os nomes dos seus arquivos não contiverem novas linhas, você poderá usar novas linhas como separador. Quando você expande a variável, primeiro desative a globbing com set -f e defina a lista de caracteres de divisão de campo IFS para 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 itens em 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ê deseja explicitamente que a divisão de campos ocorra (assim como globbing, a menos que você tenha desativado isso).

    
por 29.11.2013 / 01:47
5

Como sua variável $INPUT usa novas linhas como separadores, presumo que seus arquivos não terão novas linhas nos nomes. Como tal, sim, há uma maneira simples de iterar os arquivos e preservar o espaço em branco.

A idéia é usar o read shell embutido. Normalmente, read se dividirá em qualquer espaço em branco e, portanto, os espaços o dividirão. Mas você pode definir IFS=$'\n' e ele será dividido em novas linhas apenas. Então você pode percorrer cada linha da sua lista.

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

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 awk , que desduplica baseado no nome do arquivo (divide em / e imprime a linha se o último item não tiver sido visto antes). Então, uma vez que o awk gerou a lista de caminhos de arquivos, usamos while read para percorrer a lista.

    
por 28.11.2013 / 11:25