Saya membuat file dengan bidang-bidang yang dibatasi-tab.
echo foo$'\t'bar$'\t'baz$'\n'foo$'\t'bar$'\t'baz > input
Saya memiliki skrip berikut bernama zsh.sh
#!/usr/bin/env zsh
while read line; do
<<<$line cut -f 2
done < "$1"
Saya mengujinya.
$ ./zsh.sh input
bar
bar
Ini berfungsi dengan baik. Namun, ketika saya mengubah baris pertama bash
sebagai gantinya, itu gagal.
$ ./bash.sh input
foo bar baz
foo bar baz
Mengapa ini gagal bash
dan bekerja dengan zsh
?
Pemecahan masalah tambahan
- Menggunakan jalur langsung di shebang bukannya
env
menghasilkan perilaku yang sama. - Perpipaan dengan
echo
alih - alih menggunakan string di sini<<<$line
juga menghasilkan perilaku yang sama. yaituecho $line | cut -f 2
. - Menggunakan
awk
alih-alihcut
bekerja untuk kedua shell. yaitu<<<$line awk '{print $2}'
.
bash
zsh
quoting
whitespace
here-string
Sparhawk
sumber
sumber
echo -e 'foo\tbar\tbaz\n...'
,echo $'foo\tbar\tbaz\n...'
, atauprintf 'foo\tbar\tbaz\n...\n'
atau variasi dari ini. Ini menyelamatkan Anda dari keharusan untuk secara individual membungkus setiap tab atau baris baru.Jawaban:
Yang terjadi adalah
bash
mengganti tab dengan spasi. Anda dapat menghindari masalah ini dengan mengatakan"$line"
sebaliknya, atau dengan secara eksplisit memotong spasi.sumber
\t
dan menggantinya dengan spasi?<<< $line
,bash
tidak terpecah tetapi tidak glob. Tidak ada alasan itu akan terpecah di sini karena<<<
mengharapkan satu kata. Itu pecah dan kemudian bergabung dalam kasus itu, yang tidak masuk akal dan menentang semua implementasi shell lain yang telah mendukung<<<
sebelum atau sesudahbash
. IMO itu bug.Itu karena di
<<< $line
,bash
apakah kata splitting, (meskipun tidak globbing)$line
karena tidak dikutip di sana dan kemudian bergabung dengan kata-kata yang dihasilkan dengan karakter spasi (dan menempatkan bahwa dalam file sementara diikuti oleh karakter baris baru dan menjadikannya sebagai stdin daricut
).tab
kebetulan berada pada nilai default$IFS
:Solusi dengan
bash
adalah dengan mengutip variabel.Perhatikan bahwa hanya shell yang melakukan itu.
zsh
(Dari mana<<<
datang, terinspirasi oleh port Unix ofrc
)ksh93
,mksh
danyash
yang juga mendukung<<<
tidak melakukannya.Ketika datang ke array,
mksh
,yash
danzsh
bergabung pada karakter pertama$IFS
,bash
danksh93
pada ruang.Ada perbedaan antara
zsh
/yash
danmksh
(setidaknya versi R52) ketika$IFS
kosong:Perilaku ini lebih konsisten di seluruh shell saat Anda gunakan
"${a[*]}"
(kecuali yangmksh
masih memiliki bug saat$IFS
kosong).Di
echo $line | ...
, itulah operator split + glob yang biasa ada di semua cangkang mirip Bourne tetapizsh
(dan masalah biasa yang terkait dengannyaecho
).sumber
Masalahnya adalah Anda tidak mengutip
$line
. Untuk menyelidiki, ubah kedua skrip sehingga mereka cukup mencetak$line
:dan
Sekarang, bandingkan hasilnya:
Seperti yang Anda lihat, karena Anda tidak mengutip
$line
, tab tidak ditafsirkan dengan benar oleh bash. Zsh tampaknya berurusan dengan itu dengan lebih baik. Sekarang,cut
gunakan\t
sebagai pembatas bidang secara default. Oleh karena itu, karenabash
skrip Anda memakan tab (karena operator glob + split),cut
hanya melihat satu bidang dan bertindak sesuai dengannya. Apa yang sebenarnya Anda jalankan adalah:Jadi, agar skrip Anda berfungsi seperti yang diharapkan di kedua shell, kutip variabel Anda:
Kemudian, keduanya menghasilkan output yang sama:
sumber
bash.sh
Seperti yang telah dijawab, cara yang lebih portabel untuk menggunakan variabel adalah dengan mengutipnya:
Ada perbedaan implementasi dalam bash, dengan baris:
Ini adalah hasil dari sebagian besar cangkang:
Hanya bash pisahkan variabel di sebelah kanan
<<<
saat tidak dikutip.Namun, yang telah diperbaiki pada versi bash 4.4
Itu berarti bahwa nilai
$IFS
mempengaruhi hasil<<<
.Dengan garis:
Semua shell menggunakan karakter pertama IFS untuk menggabungkan nilai.
Dengan
"${l[@]}"
, ruang diperlukan untuk memisahkan argumen yang berbeda, tetapi beberapa shell memilih untuk menggunakan nilai dari IFS (Apakah itu benar?).Dengan IFS nol, nilai-nilai harus bergabung, seperti dengan baris ini:
Tetapi baik lksh dan mksh gagal melakukannya.
Jika kami mengubah ke daftar argumen:
Baik yash dan zsh gagal memisahkan argumen. Apakah itu bug?
sumber
zsh
/yash
dan"${l[@]}"
dalam konteks non-daftar, itu dengan desain di mana"${l[@]}"
hanya khusus dalam konteks daftar. Dalam konteks non-daftar, tidak ada pemisahan yang mungkin terjadi, Anda harus bergabung dengan elemen tersebut entah bagaimana. Bergabung dengan karakter pertama $ IFS lebih konsisten daripada bergabung dengan karakter ruang IMO.dash
melakukannya juga (dash -c 'IFS=; a=$@; echo "$a"' x a b
). Namun POSIX berniat untuk mengubah IIRC itu. Lihat diskusi ini (panjang)var=$@
tidak ditentukan.