baca -a array -d '\ n' <foo, kode keluar 1

8

Jika saya mencoba mengeksekusi

read -a fooArr -d '\n' < bar

kode keluarnya adalah 1 - meskipun itu memenuhi apa yang saya inginkan; menempatkan setiap baris bardalam elemen array fooArr(menggunakan bash 4.2.37).

Adakah yang bisa menjelaskan mengapa ini terjadi?


Saya telah menemukan cara lain untuk menyelesaikan ini, seperti yang di bawah ini, jadi bukan itu yang saya minta.

for ((i=1;; i++)); do
    read "fooArr$i" || break;
done < bar

atau

mapfile -t fooArr < bar
RasmusWL
sumber

Jawaban:

14

Yang perlu dijelaskan adalah bahwa perintah itu tampaknya berfungsi, bukan kode keluarnya

'\n'adalah dua karakter: garis miring terbalik \dan satu huruf n. Apa yang Anda pikir Anda butuhkan adalah $'\n', yang merupakan linefeed (tetapi itu juga tidak benar, lihat di bawah).

The -dopsi melakukan hal ini:

  -d delim  continue until the first character of DELIM is read, rather
            than newline

Jadi tanpa opsi itu, readakan membaca hingga baris baru, membagi garis menjadi kata-kata menggunakan karakter $IFSsebagai pemisah, dan memasukkan kata-kata ke dalam array. Jika Anda menentukan -d $'\n', mengatur pembatas garis ke baris baru, itu akan melakukan hal yang persis sama . Pengaturan -d '\n'berarti bahwa ia akan membaca hingga backslash pertama (tetapi, sekali lagi, lihat di bawah), yang merupakan karakter pertama di delim. Karena tidak ada garis miring terbalik di file Anda, maka readakan berakhir pada akhir file, dan:

Exit Status:
The return code is zero, unless end-of-file is encountered, read times out,
or an invalid file descriptor is supplied as the argument to -u.

Jadi itu sebabnya kode keluar adalah 1.

Dari fakta bahwa Anda percaya bahwa perintah itu berfungsi, kita dapat menyimpulkan bahwa tidak ada spasi di file, sehingga read, setelah membaca seluruh file dengan harapan sia-sia menemukan backslash, akan membaginya dengan spasi putih (nilai default dari $IFS), termasuk baris baru. Jadi setiap baris (atau setiap kata, jika satu baris berisi lebih dari satu kata) disimpan ke dalam array.

Kasus misterius backslash purloined

Sekarang, bagaimana saya tahu file tersebut tidak mengandung garis miring terbalik? Karena Anda tidak memasok -rbendera ke read:

  -r                do not allow backslashes to escape any characters

Jadi, jika Anda memiliki garis miring terbalik di file, mereka akan dilucuti, kecuali jika Anda memiliki dua dari mereka berturut-turut. Dan, tentu saja, ada bukti yang readmemiliki kode keluar 1, yang menunjukkan bahwa itu tidak menemukan backslash, jadi tidak ada dua dari mereka berturut-turut.

Takeaways

Bash tidak akan menjadi bash jika tidak ada gotchas bersembunyi di balik setiap perintah, dan readtidak terkecuali. Berikut adalah pasangannya:

  1. Kecuali Anda tentukan -r, readakan menafsirkan urutan melarikan diri backslash. Kecuali jika memang itu yang Anda inginkan (yang memang sesekali, tetapi hanya sesekali), Anda harus ingat untuk menentukan -ragar karakter tidak hilang dalam kasus yang jarang ada backslash pada input.

  2. Fakta yang readmengembalikan kode keluar 1 tidak berarti gagal. Mungkin berhasil, kecuali untuk menemukan terminator garis. Jadi hati-hati dengan loop seperti ini: while read -r LINE; do something with LINE; done karena akan gagal do somethingdengan baris terakhir dalam kasus yang jarang terjadi bahwa baris terakhir tidak memiliki baris baru di akhir.

  3. read -r LINE mempertahankan garis miring terbalik, tetapi itu tidak mempertahankan spasi spasial memimpin atau tertinggal.

rici
sumber
Terima kasih! Kukira aku sudah mencoba pendekatan $ '\ n', tapi kurasa tidak: / senang tahu tentang -r
RasmusWL
2
"kasus yang jarang terjadi bahwa baris terakhir tidak memiliki baris baru" bagi saya tampaknya bukan alasan kuat untuk menyarankan "jangan pernah menggunakan while read". Kalau tidak, jawaban yang fantastis.
glenn jackman
@glennjackman: Anda dapat menggunakan while readselama Anda tidak memiliki apa pun di dalam loop. Kalau tidak, itu bug yang menunggu untuk menggigit Anda - dan percayalah, saya sudah digigit.
rici
2
Baris terakhir selalu memiliki baris baru. Begitulah garis didefinisikan: itu berakhir dengan baris baru. Jika file yang tidak kosong tidak diakhiri dengan baris baru, itu bukan file teks, dan Anda tidak dapat menggunakan alat pemrosesan teks seperti utilitas shell read.
Gilles 'SANGAT berhenti menjadi jahat'
2
@glennjackman: Saya, saya beralih melalui file columnised dengan awk, sebagian besar. Untuk iterating seluruh baris di mana file tidak terlalu besar, mapfilecukup keren. Untuk hack cepat & kotor, saya akan menggunakan loop terlarang sementara, tapi saya sudah berhenti memasukkannya ke dalam skrip produksi. YMMV dan saya melunakkan peringatan dalam jawabannya.
rici
4

Itu adalah perilaku yang diharapkan:

Kode pengembalian adalah nol, kecuali jika file akhir ditemukan, [...]

start cmd:> echo a b c | { read -a testarray; echo $?; }
0

start cmd:> echo -n a b c | { read -a testarray; echo $?; }
1
Hauke ​​Laging
sumber