Mam zmienną, która zawiera wieloliniowe wyjście polecenia. Jaki jest najskuteczniejszy sposób odczytywania danych wyjściowych linia po linii ze zmiennej?
Na przykład:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Możesz użyć pętli while z podstawieniem procesu:
while read -r line
do
echo "$line"
done < <(jobs)
Optymalnym sposobem odczytu zmiennej wielowierszowej jest ustawienie pustej IFS
zmiennej i printf
zmiennej z końcowym znakiem nowej linii:
# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
echo "$line"
done < <(printf '%s\n' "$var")
Uwaga: Zgodnie z shellcheck sc2031 użycie podstacji procesu jest lepsze niż potok, aby uniknąć [subtelnie] tworzenia podpowłoki.
Pamiętaj również, że nazwanie zmiennej jobs
może powodować zamieszanie, ponieważ jest to również nazwa zwykłego polecenia powłoki.
while IFS= read
.... Jeśli chcesz zapobiec \ interpretacji, użyjread -r
echo
naprintf %s
, aby twój skrypt działał nawet przy nieposkromionym wejściu./tmp
zapisu katalogu, ponieważ polega on na możliwości utworzenia tymczasowego pliku roboczego. Jeśli kiedykolwiek znajdziesz się w systemie z ograniczonym/tmp
dostępem, który jest tylko do odczytu (i nie możesz go zmienić), będziesz zadowolony z możliwości zastosowania alternatywnego rozwiązania, np. Zprintf
rurką.printf "%s\n" "$var" | while IFS= read -r line
Aby przetworzyć dane wyjściowe wiersza polecenia po wierszu ( wyjaśnienie ):
Jeśli masz już dane w zmiennej:
printf %s "$foo"
jest prawie identycznyecho "$foo"
, ale drukuje$foo
dosłownie,echo "$foo"
ale może interpretować$foo
jako opcję polecenia echo, jeśli zaczyna się od-
, i może rozszerzać sekwencje odwrotnego ukośnika$foo
w niektórych powłokach.Zauważ, że w niektórych powłokach (ash, bash, pdksh, ale nie ksh lub zsh) prawa strona potoku działa w osobnym procesie, więc każda zmienna ustawiona w pętli zostaje utracona. Na przykład poniższy skrypt zliczający wiersze wypisuje 0 w tych powłokach:
Obejściem tego problemu jest umieszczenie pozostałej części skryptu (lub przynajmniej części wymagającej wartości
$n
z pętli) na liście poleceń:Jeśli działanie na niepustych liniach jest wystarczająco dobre, a dane wejściowe nie są ogromne, możesz użyć podziału słów:
Objaśnienie: ustawienie
IFS
pojedynczej nowej linii powoduje, że dzielenie słów występuje tylko w nowej linii (w przeciwieństwie do dowolnego znaku spacji w ustawieniu domyślnym).set -f
wyłącza masek (tj dopasowywanie), które w przeciwnym razie zdarzyć się w wyniku substytucji poleceń$(jobs)
lub zmienna substitutino$foo
.for
Pętla działa na wszystkich kawałków$(jobs)
, które są wszystkie niepuste linie wyjścia polecenia. Na koniec przywróć globowanie iIFS
ustawienia.źródło
local IFS=something
. Nie wpłynie to na wartość zasięgu globalnego. IIRCunset IFS
nie przywraca ustawień domyślnych (i na pewno nie działa, jeśli wcześniej nie był domyślny).Problem: jeśli użyjesz pętli while, będzie ona działać w podpowłoce i wszystkie zmienne zostaną utracone. Rozwiązanie: użyj dla pętli
źródło
while read
pętli w bash oznacza, że pętla while znajduje się w podpowłoce, więc zmienne nie są globalne.while read;do ;done <<< "$var"
sprawia, że ciało pętli nie jest podpowłoką. (Ostatnie bash ma opcję umieszczenia treścicmd | while
pętli nie w podpowłoce, jak zawsze miało to miejsce w ksh.)W najnowszych wersjach bash użyj
mapfile
lub,readarray
aby efektywnie odczytać dane wyjściowe poleceń do tablicOświadczenie: okropny przykład, ale możesz wymyślić lepszą komendę do użycia niż sam
źródło
readarray
funkcję i wywołaj funkcję kilka razy.Bibliografia:
while
IFS
read
źródło
-r
to także dobry pomysł; Zapobiega\` interpretation... (it is in your links, but its probably worth mentioning, just to round out your
IFS = `(co jest niezbędne, aby zapobiec utracie białych znaków)Możesz użyć <<<, aby po prostu odczytać ze zmiennej zawierającej dane oddzielone znakiem nowej linii:
źródło