W tych zaskakująco częstych przypadkach, w których to, co naprawdę musisz zrobić, jest przetworzenie wszystkich niepustych linii w zmiennej w pewien sposób (w tym ich zliczanie), możesz ustawić IFS tylko na nową linię, a następnie użyć mechanizmu dzielenia słów powłoki, aby przerwać niepuste linie od siebie.
Na przykład, oto mała funkcja powłoki, która sumuje niepuste linie we wszystkich dostarczonych argumentach:
lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)
Nawiasy, a nie nawiasy klamrowe, są tutaj używane do utworzenia polecenia złożonego dla treści funkcji. Powoduje to, że funkcja jest wykonywana w podpowłoce, dzięki czemu nie zanieczyszcza zewnętrznych zmiennych IFS i ustawienia rozwijania nazw ścieżek przy każdym wywołaniu.
Jeśli chcesz iterować po niepustych liniach, możesz to zrobić podobnie:
IFS='
'
set -f
for line in $lines
do
printf '[%s]\n' $line
done
Manipulowanie IFS w ten sposób jest często pomijaną techniką, przydatną również do robienia takich rzeczy, jak parsowanie nazw ścieżek, które mogą zawierać spacje z kolumnowych danych rozdzielanych tabulatorami. Należy jednak pamiętać, że celowe usunięcie znaku spacji zwykle zawartego w domyślnym ustawieniu IFS spacji-tab-nowej linii może spowodować wyłączenie podziału słów w miejscach, w których normalnie można się tego spodziewać.
Na przykład, jeśli używasz zmiennych do zbudowania skomplikowanego wiersza poleceń dla czegoś podobnego ffmpeg
, możesz chcieć uwzględnić -vf scale=$scale
tylko wtedy, gdy zmienna scale
jest ustawiona na coś niepustego. Zwykle można to osiągnąć za pomocą, ${scale:+-vf scale=$scale}
ale jeśli IFS nie zawiera zwykłego znaku spacji w czasie, gdy rozszerzenie parametru jest wykonywane, odstęp między -vf
i scale=
nie będzie używany jako separator słów i ffmpeg
zostanie przekazany -vf scale=$scale
jako pojedynczy argument, którego nie zrozumie.
Aby ustalić, że ci, że albo trzeba się upewnić, IFS postawiono bardziej zwykle przed wykonaniem ${scale}
ekspansji, czy dwa rozszerzenia: ${scale:+-vf} ${scale:+scale=$scale}
. Słowo dzielenie, które powłoka wykonuje podczas wstępnego analizowania wierszy poleceń, w przeciwieństwie do dzielenia, które wykonuje podczas fazy ekspansji przetwarzania tych wierszy poleceń, nie zależy od IFS.
Coś innego, co może być warte twojego czasu, jeśli masz zamiar to zrobić, to utworzenie dwóch globalnych zmiennych powłoki, które będą zawierać tylko tabulator i tylko nową linię:
t=' '
n='
'
W ten sposób możesz po prostu włączać $t
i $n
rozszerzać, gdzie potrzebujesz tabulatorów i znaków nowej linii, zamiast zaśmiecać cały kod cytowanym białym znakiem. Jeśli wolisz całkowicie unikać cytowanych białych znaków w powłoce POSIX, która nie ma do tego żadnego innego mechanizmu, printf
możesz pomóc, choć potrzebujesz trochę błahostki, aby obejść usuwanie końcowych znaków nowej linii w rozszerzeniach poleceń:
nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}
Czasami ustawienie IFS tak, jakby była zmienną środowiskową na polecenie, działa dobrze. Na przykład, oto pętla, która odczytuje nazwę ścieżki, która może zawierać spacje i współczynnik skalowania z każdej linii pliku wejściowego rozdzielanego tabulatorami:
while IFS=$t read -r path scale
do
ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt
W tym przypadku read
wbudowany widzi IFS ustawiony tylko na tabulator, więc nie podzieli linii wejściowej, którą odczytuje również na spacje. Ale IFS=$t set -- $lines
nie działa: powłoka rozwija się, $lines
gdy buduje set
argumenty wbudowane przed wykonaniem polecenia, więc tymczasowe ustawienie IFS w sposób, który ma zastosowanie tylko podczas wykonywania samego wbudowanego, przychodzi za późno. Właśnie dlatego fragmenty kodu, które podałem, przede wszystkim ustawiają IFS w osobnym kroku i dlatego muszą zająć się kwestią jego zachowania.
wc -l
jest dokładnie równoważne z oryginałem:<<<$foo
dodaje nowy wiersz do wartości$foo
(nawet jeśli$foo
był pusty). W mojej odpowiedzi wyjaśniam, dlaczego być może nie tego chciałem, ale o to pytano.