Membaca karakter demi karakter dengan bash read


8

Saya sudah mencoba menggunakan bash untuk membaca file karakter demi karakter.

Setelah banyak percobaan dan kesalahan, saya menemukan bahwa ini berfungsi:

exec 4<file.txt 
declare -i n
while read -r ch <&4; 
     n=0
     while [ ! $n -eq ${#ch} ]
           do  echo -n "${ch:$n:1}"
               (( n++ ))
          done
     echo "" 
     done

Yaitu, saya bisa membacanya baris demi baris dan kemudian mengulangi setiap baris char dengan char.

Sebelum melakukan ini, saya telah mencoba: exec 4<file.txt && while read -r -n1 ch <&4; do; echo -n "$ch"; done tetapi itu akan melewatkan semua spasi putih di file .

Bisakah Anda jelaskan mengapa? Apakah ada cara untuk membuat strategi kedua (yaitu membaca char by char dengan bash's read) berfungsi?


4
Tetapkan IFSapa-apa agar spasi putih bertahan dari pemisahan kata.
manatwork

Sudah mencoba itu dengan IFS = '', tapi saya kira itu hanya IFS =. Terima kasih!
PSkocik

Jawaban:


12

Anda perlu menghapus karakter spasi dari $IFSparameter untuk readberhenti melompati karakter yang memimpin dan mengekor (dengan -n1, karakter spasi jika ada yang akan memimpin dan mengekor, jadi dilewati):

while IFS= read -rn1 a; do printf %s "$a"; done

Tetapi meskipun demikian, bash's readakan melewati karakter baris baru, yang dapat Anda atasi:

while IFS= read -rn1 a; do printf %s "${a:-$'\n'}"; done

Meskipun Anda dapat menggunakan IFS= read -d '' -rn1sebagai gantinya atau bahkan lebih baik IFS= read -N1(ditambahkan pada 4.1, disalin dari ksh93(ditambahkan dalam o)) yang merupakan perintah untuk membaca satu karakter.

Perhatikan bahwa bash readtidak dapat mengatasi karakter NUL. Dan ksh93 memiliki masalah yang sama dengan bash.

Dengan zsh:

while read -ku0 a; do print -rn -- "$a"; done

(zsh dapat mengatasi karakter NUL).

Perhatikan bahwa mereka read -k/n/Nmembaca sejumlah karakter , bukan byte . Jadi untuk karakter multibyte, mereka mungkin harus membaca beberapa byte hingga karakter penuh dibaca. Jika input berisi karakter yang tidak valid, Anda mungkin berakhir dengan variabel yang berisi urutan byte yang tidak membentuk karakter yang valid dan yang shell akhirnya menghitung sebagai beberapa karakter . Misalnya di lokal UTF-8:

$ printf '\375\200\200\200\200ABC' | bash -c '
    IFS= read  -rN1 a; echo "${#a}"'
6

Itu \375akan memperkenalkan karakter UTF-8 6-byte. Namun, yang ke-6 ( A) di atas tidak valid untuk karakter UTF-8. Anda masih berakhir dengan \375\200\200\200\200Adi $a, yang bashdihitung sebagai 6 karakter meskipun 5 yang pertama tidak benar-benar karakter, hanya 5 byte yang tidak membentuk bagian dari karakter apa pun.


Terima kasih. Sederhana dan indah. Saya benar-benar mencoba sesuatu untuk tujuan ini (memodifikasi variabel IFS), tapi itu agak tidak berhasil bagi saya jadi saya berakhir dengan ramuan itu (tidak perlu bermain dengan deskriptor file, dll.).
PSkocik

1
Menariknya, sepertinya menggunakan read -rN1bukan memecahkan masalah baris baru dan dengan demikian menghilangkan perlu memberikan baris baru sebagai default saat mencetak $a.
krb686

Hanya FTR saya sedang membaca file 4118 baris 20 MB. Menggunakan read -n1(char by char) membutuhkan waktu 4 menit 51 detik dan memanaskan laptop hingga 90 derajat. Menggunakan read -r(baris demi baris) membutuhkan 1,3 detik dan laptop tetap pada 54 derajat dengan kipas ganda diam.
WinEunuuchs2Unix

2

Ini adalah contoh sederhana menggunakan cut, forlingkaran & wc:

bytes=$(wc -c < /etc/passwd)
file=$(</etc/passwd)

for ((i=0; i<bytes; i++)); do
    echo $file | cut -c $i
done

CIUMAN bukan?


Jika itu kiss, lalu apa adalah murni bashsolusi: file="$(</etc/passwd)"; bytes="${#file}"; for ((i=0;i<bytes;i++)); do echo "${file:i:1}"; done?
manatwork

Terima kasih untuk keduanya. Ya, jika saya harus mengambil karakter-karakter itu dari baris, saya mungkin juga mendapatkannya dari keseluruhan file. Saya menemukan solusi sch yang paling KISS.
PSkocik

@manatwork Itu solusi yang bagus dan sederhana. Meski begitu, menurut saya jawaban di atas menggunakan loop baca sedikit lebih cepat untuk beberapa alasan. Mungkin substring dalam bash cukup lambat?
krb686

@ krb686, sebenarnya keseluruhan bash“Ini terlalu besar dan terlalu lambat.” menurut bagian BUGS halaman manualnya. Namun demikian, masih lebih cepat untuk mengiris string dalam memori daripada membaca file lagi dan lagi untuk setiap karakter. Setidaknya di mesin saya: pastebin.com/zH5trQQs
manatwork
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.