Saya memiliki variabel yang berisi output multiline dari suatu perintah. Apa cara paling efisien untuk membaca output baris demi baris dari variabel?
Sebagai contoh:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Saya memiliki variabel yang berisi output multiline dari suatu perintah. Apa cara paling efisien untuk membaca output baris demi baris dari variabel?
Sebagai contoh:
jobs="$(jobs)"
if [ "$jobs" ]; then
# read lines from $jobs
fi
Jawaban:
Anda dapat menggunakan loop sementara dengan substitusi proses:
while read -r line
do
echo "$line"
done < <(jobs)
Cara optimal untuk membaca variabel multiline adalah dengan menetapkan IFS
variabel kosong dan printf
variabel dengan baris baru:
# 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")
Catatan: Sesuai shellcheck sc2031 , penggunaan subtitusi proses lebih disukai daripada pipa untuk menghindari [secara halus] membuat subkulit.
Juga, harap menyadari bahwa dengan memberi nama variabel jobs
itu dapat menyebabkan kebingungan karena itu juga nama perintah shell umum.
echo
menjadi printf %s
, sehingga skrip Anda akan berfungsi bahkan dengan input yang tidak jinak.
/tmp
direktori dapat ditulis, karena ini bergantung pada kemampuan untuk membuat file kerja sementara. Jika Anda menemukan diri Anda pada sistem terbatas dengan /tmp
hanya-baca (dan tidak dapat diubah oleh Anda), Anda akan senang tentang kemungkinan menggunakan solusi alternatif, misalnya dengan printf
pipa.
printf "%s\n" "$var" | while IFS= read -r line
Untuk memproses output baris perintah demi baris ( penjelasan ):
jobs |
while IFS= read -r line; do
process "$line"
done
Jika Anda sudah memiliki data dalam variabel:
printf %s "$foo" | …
printf %s "$foo"
hampir identik dengan echo "$foo"
, tetapi dicetak $foo
secara harfiah, sedangkan echo "$foo"
mungkin mengartikan $foo
sebagai opsi untuk perintah gema jika dimulai dengan -
, dan mungkin memperluas urutan backslash di $foo
dalam beberapa shell.
Perhatikan bahwa dalam beberapa shell (abu, bash, pdksh, tetapi bukan ksh atau zsh), sisi kanan pipa berjalan dalam proses terpisah, sehingga variabel apa pun yang Anda atur dalam loop hilang. Misalnya, skrip penghitungan baris berikut mencetak 0 pada shell ini:
n=0
printf %s "$foo" |
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
Solusinya adalah dengan meletakkan sisa skrip (atau setidaknya bagian yang membutuhkan nilai dari $n
loop) dalam daftar perintah:
n=0
printf %s "$foo" | {
while IFS= read -r line; do
n=$(($n + 1))
done
echo $n
}
Jika bertindak pada baris yang tidak kosong cukup baik dan inputnya tidak besar, Anda dapat menggunakan pemisahan kata:
IFS='
'
set -f
for line in $(jobs); do
# process line
done
set +f
unset IFS
Penjelasan: pengaturan IFS
ke baris baru tunggal membuat pemisahan kata hanya terjadi di baris baru (sebagai lawan dari karakter spasi putih di bawah pengaturan default). set -f
mematikan globbing (yaitu ekspansi wildcard), yang jika tidak akan terjadi pada hasil substitusi perintah $(jobs)
atau substitusi variabel $foo
. The for
Loop bekerja pada semua bagian $(jobs)
, yang semua garis non-kosong di output perintah. Terakhir, kembalikan globbing dan IFS
pengaturan.
local IFS=something
. Itu tidak akan mempengaruhi nilai lingkup global. IIRC, unset IFS
tidak membuat Anda kembali ke default (dan tentu saja tidak berfungsi jika itu bukan default sebelumnya).
Masalah: jika Anda menggunakan while, itu akan berjalan dalam subkulit dan semua variabel akan hilang. Solusi: gunakan untuk loop
# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'
for line in $variableWithSeveralLines; do
echo "$line"
# return IFS back if you need to split new line by spaces:
IFS=$IFS_BAK
IFS_BAK=
lineConvertedToArraySplittedBySpaces=( $line )
echo "{lineConvertedToArraySplittedBySpaces[0]}"
# return IFS back to newline for "for" loop
IFS_BAK=$IFS
IFS=$'\n'
done
# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=
while read
loop di bash berarti loop sementara dalam subkulit, sehingga variabel tidak global. while read;do ;done <<< "$var"
membuat loop body bukan subshell. (Bash baru-baru ini memiliki opsi untuk menempatkan tubuh cmd | while
loop tidak dalam subkulit, seperti yang selalu dimiliki ksh.)
Dalam versi bash terbaru, gunakan mapfile
atau readarray
untuk secara efisien membaca output perintah ke dalam array
$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305
Penafian: contoh yang mengerikan, tetapi Anda dapat dengan cepat menghasilkan perintah yang lebih baik untuk digunakan daripada diri Anda sendiri
readarray
fungsi dan memanggil fungsi beberapa kali.
jobs="$(jobs)"
while IFS= read
do
echo $REPLY
done <<< "$jobs"
Referensi:
-r
juga merupakan ide bagus; Ini mencegah \` interpretation... (it is in your links, but its probably worth mentioning, just to round out your
IFS = `(yang sangat penting untuk mencegah kehilangan spasi putih)
Anda dapat menggunakan <<< untuk membaca dari variabel yang berisi data yang dipisahkan dengan baris baru:
while read -r line
do
echo "A line of input: $line"
done <<<"$lines"
while IFS= read
.... Jika Anda ingin mencegah \ interpretasi, maka gunakanread -r