Zgodny z POSIX sposób pracy z listą nazw plików, ewentualnie z białymi znakami

14

Widziałem przewodniki skryptów Bash sugerujące użycie tablicy do pracy z nazwami plików zawierającymi białe znaki. DashAsBinSh sugeruje jednak, że tablice nie są przenośne, dlatego szukam zgodnego z POSIX sposobu pracy z listami nazw plików, które mogą zawierać spacje.

Chcę zmodyfikować poniższy przykładowy skrypt, aby to zrobił echo

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

Oto skrypt

#!/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
Eero Aaltonen
źródło
Możliwe to samo na SO: stackoverflow.com/questions/6499486/...
Ciro Santilli 28 改造 中心 法轮功 六四 事件

Odpowiedzi:

8

Powłoki POSIX jeden układ: parametry pozycyjne ( $1, $2itp zbiorczo określane jako "$@").

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

Jest to niewygodne, ponieważ jest tylko jeden i niszczy wszelkie inne użycie parametrów pozycyjnych. Parametry pozycyjne są lokalne dla funkcji, która czasem jest błogosławieństwem, a czasem przekleństwem.

Jeśli masz pewność, że twoje nazwy plików nie będą zawierać nowych linii, możesz użyć nowej linii jako separatora. Po rozwinięciu zmiennej najpierw wyłącz globowanie za pomocą set -fi ustaw listę znaków dzielących pola tak, IFSaby zawierała tylko nową linię.

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

Ponieważ elementy na liście są oddzielone znakami nowej linii, możesz w szczególności używać wielu poleceń przetwarzania tekstu sort.

Pamiętaj, aby zawsze umieszczać podwójne cudzysłowy wokół podstawień zmiennych, z wyjątkiem sytuacji, gdy jawnie chcesz, aby zachodziło dzielenie pól (a także globowanie, chyba że to wyłączyłeś).

Gilles „SO- przestań być zły”
źródło
Dobra odpowiedź i wyjaśnienie. Oznaczę to jako zaakceptowane, ponieważ dzięki temu oryginalny sort | uniqkrok działa zgodnie z przeznaczeniem.
Eero Aaltonen,
5

Ponieważ twoja $INPUTzmienna używa znaków nowej linii jako separatorów, zakładam, że twoje pliki nie będą miały nowych linii w nazwach. Jako taki, tak, istnieje prosty sposób na iterację plików i zachowanie białych znaków.

Chodzi o to, aby użyć readwbudowanej powłoki. Normalnie readdzieli się na dowolne białe spacje, więc spacje będą go łamać. Ale możesz ustawić, IFS=$'\n'a zamiast tego podzieli się tylko na nowe linie. Możesz iterować po każdej linii na liście.

Oto najmniejsze rozwiązanie, jakie mogłem wymyślić:

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

Zasadniczo wysyła „$ INPUT”, do awkktórego deduplikuje się na podstawie nazwy pliku (dzieli się, /a następnie drukuje wiersz, jeśli ostatni element nie był wcześniej widziany). Następnie, gdy awk wygeneruje listę ścieżek do plików, używamy while readdo iteracji po liście.

Patrick
źródło
$ checkbashisms bar.sh możliwy bashism w bar.sh wiersz 14 (<<< tutaj string)
Eero Aaltonen
1
@EeroAaltonen Zmieniono, aby nie używało ciągu znaków. Zauważ jednak, że przy tej zmianie whilepętla, a zatem dostuffwithjest wykonywana w podpowłoce. Tak więc wszelkie zmienne lub zmiany wprowadzone do działającej powłoki zostaną utracone po zakończeniu pętli. Jedyną alternatywą jest użycie pełnego heredoka, co nie jest takie nieprzyjemne, ale pomyślałem, że byłoby to lepsze.
Patrick,
Przyznam punkty bardziej na podstawie czytelności niż małości. To z pewnością działa i już za to +1.
Eero Aaltonen
IFS="\n"dzieli na odwrotny ukośnik i n znaków. Ale w read fileśrodku nie ma podziału. IFS="\n"jest nadal użyteczny, ponieważ usuwa puste znaki z $ IFS, które w innym przypadku zostałyby usunięte na początku i na końcu danych wejściowych. Aby odczytać wiersz, kanoniczna składnia jest IFS= read -r linejednak IFS=anything read -r line(pod warunkiem , że nic nie zawiera spacji) również będzie działać.
Stéphane Chazelas,
ups. Nie jestem pewien, jak mi się to udało. Naprawiony.
Patrick,