Bagaimana cara menggunakan skrip bash untuk membaca konten file biner?

15

Saya ingin membaca karakter dan kemudian panjang string tetap (string tidak null diakhiri dalam file, dan panjangnya diberikan oleh karakter sebelumnya).

Bagaimana saya bisa melakukan ini dalam skrip bash? Bagaimana cara mendefinisikan variabel string sehingga saya bisa melakukan beberapa post-processing di atasnya?

Amanda
sumber

Jawaban:

19

Jika Anda ingin tetap menggunakan utilitas shell, Anda dapat menggunakan headuntuk mengekstrak sejumlah byte, dan oduntuk mengubah byte menjadi angka.

export LC_ALL=C    # make sure we aren't in a multibyte locale
n=$(head -c 1 | od -An -t u1)
string=$(head -c $n)

Namun, ini tidak berfungsi untuk data biner. Ada dua masalah:

  • Substitusi perintah $(…)menghapus baris baru dalam output perintah. Ada solusi yang cukup mudah: pastikan output berakhir pada karakter selain dari baris baru, lalu hapus satu karakter itu.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash, seperti kebanyakan shell, buruk dalam berurusan dengan null byte . Pada bash 4.1, null byte hanya dijatuhkan dari hasil substitusi perintah. Dash 0.5.5 dan pdksh 5.2 memiliki perilaku yang sama, dan ATT ksh berhenti membaca pada byte nol pertama. Secara umum, cangkang dan utilitasnya tidak diarahkan untuk menangani file biner. (Zsh adalah pengecualian, ini dirancang untuk mendukung byte nol.)

Jika Anda memiliki data biner, Anda ingin beralih ke bahasa seperti Perl atau Python.

<input_file perl -e '
  read STDIN, $c, 1 or die $!;    # read length byte
  $n = read STDIN, $s, ord($c);   # read data
  die $! if !defined $n;
  die "Input file too short" if ($n != ord($c));
  # Process $s here
'
<input_file python -c '
  import sys
  n = ord(sys.stdin.read(1))      # read length byte
  s = sys.stdin.read(n)           # read data
  if len(s) < n: raise ValueError("input file too short")
  # Process s here
'
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Skrip shell +1 tidak selalu sesuai
forcefsck
2
exec 3<binary.file     # open the file for reading on file descriptor 3
IFS=                   #
read -N1 -u3 char      # read 1 character into variable "char"

# to obtain the ordinal value of the char "char"
num=$(printf %s "$char" | od -An -vtu1 | sed 's/^[[:space:]]*//')

read -N$num -u3 str    # read "num" chars
exec 3<&-              # close fd 3
glenn jackman
sumber
5
read -Nberhenti pada byte nol, jadi ini bukan cara yang cocok untuk bekerja dengan data biner. Secara umum, cangkang selain zsh tidak dapat mengatasi nulls.
Gilles 'SO- berhenti menjadi jahat'
2

Jika Anda ingin dapat menangani file biner di shell, opsi terbaik (hanya?) Adalah bekerja dengan alat hexdump .

hexdump -v -e '/1 "%u\n"' binary.file | while read c; do
  echo $c
done

Hanya baca X byte:

head -cX binary.file | hexdump -v -e '/1 "%u\n"' | while read c; do
  echo $c
done

Baca panjang (dan bekerja dengan 0 sebagai panjang) dan kemudian "string" sebagai nilai desimal byte:

len=$(head -c1 binary.file | hexdump -v -e '/1 "%u\n"')
if [ $len -gt 0 ]; then
  tail -c+2 binary.file | head -c$len | hexdump -v -e '/1 "%u\n"' | while read c; do
    echo $c
  done
fi
Clément Moulin - SimpleRezo
sumber
Daripada hanya menyajikan banyak perintah, dapatkah Anda menjelaskan apa yang mereka lakukan dan bagaimana cara kerjanya? Apa yang dimaksud dengan opsi? Output apa yang bisa diharapkan pengguna dari perintah Anda? Tolong jangan menanggapi dalam komentar; edit  jawaban Anda untuk membuatnya lebih jelas dan lebih lengkap.
G-Man Mengatakan 'Reinstate Monica'
2
Yah, saya bisa menyalin halaman manual di sini, tapi saya tidak mengerti intinya. Hanya ada perintah dasar yang digunakan di sini, satu-satunya trik adalah penggunaan hexdump.
Clément Moulin - SimpleRezo
2
Mengalami downvoting karena Anda tidak menyukai / mengerti jawaban saya, serius?
Clément Moulin - SimpleRezo
1

PEMBARUAN (dengan melihat ke belakang): ... Pertanyaan / jawaban ini (jawaban saya) membuat saya berpikir tentang anjing yang terus mengejar mobil .. Suatu hari, akhirnya, dia mengejar ke mobil .. Oke, dia menangkapnya, tapi dia benar-benar tidak bisa berbuat banyak dengan itu ... Anser ini 'menangkap' string, tetapi kemudian Anda tidak bisa berbuat banyak dengan mereka, jika mereka telah menanamkan null-byte ... (jadi +1 besar untuk Gilles menjawab .. bahasa lain mungkin ada di sini.)

ddmembaca setiap dan semua data ... Ini tentu tidak akan menghalangi nol sebagai "panjang" ... tetapi jika Anda memiliki \ x00 di mana pun dalam data Anda, Anda harus kreatif bagaimana Anda menanganinya;ddtidak memiliki masalah dengan itu, tetapi skrip shell Anda akan memiliki masalah (tapi itu tergantung pada apa yang ingin Anda lakukan dengan data) ... Berikut ini pada dasarnya output setiap "string data", ke file dengan garis pembagi antara setiap strin ...

btw: Anda mengatakan "karakter", dan saya berasumsi maksud Anda "byte" ...
tetapi kata "karakter" telah menjadi ambigu pada zaman UNICODE, di mana hanya set karakter ASCII 7-bit yang menggunakan byte tunggal per karakter ... Dan bahkan dalam sistem Unicode, jumlah byte bervariasi tergantung pada metode pengkodean karakter , misalnya. UTF-8, UTF-16, dll.

Berikut ini adalah skrip sederhana untuk menyoroti perbedaan antara "karakter" Teks dan byte.

STRING="௵"  
echo "CHAR count is: ${#STRING}"  
echo "BYTE count is: $(echo -n $STRING|wc -c)" 
# CHAR count is: 1
# BYTE count is: 3  # UTF-8 ecnoded (on my system)

Jika karakter panjang Anda adalah 1 byte panjang dan menunjukkan panjang byte , maka skrip ini harus melakukan trik, bahkan jika data berisi karakter Unicode ... ddhanya melihat byte terlepas dari pengaturan lokal apa pun ...

Skrip ini digunakan dduntuk membaca file biner dan menampilkan string yang dipisahkan oleh pembagi "====" ... Lihat skrip berikutnya untuk data pengujian

#   
div="================================="; echo $div
((skip=0)) # read bytes at this offset
while ( true ) ; do
  # Get the "length" byte
  ((count=1)) # count of bytes to read
  dd if=binfile bs=1 skip=$skip count=$count of=datalen 2>/dev/null
  (( $(<datalen wc -c) != count )) && { echo "INFO: End-Of-File" ; break ; }
  strlen=$((0x$(<datalen xxd -ps)))  # xxd is shipped as part of the 'vim-common' package
  #
  # Get the string
  ((count=strlen)) # count of bytes to read
  ((skip+=1))      # read bytes from and including this offset
  dd if=binfile bs=1 skip=$skip count=$count of=dataline 2>/dev/null
  ddgetct=$(<dataline wc -c)
  (( ddgetct != count )) && { echo "ERROR: Line data length ($ddgetct) is not as expected ($count) at offset ($skip)." ; break ; }
  echo -e "\n$div" >>dataline # add a newline for TEST PURPOSES ONLY...
  cat dataline
  #
  ((skip=skip+count))  # read bytes from and including this offset
done
#   
echo

keluar

Skrip ini membuat data uji yang mencakup awalan 3-byte per baris ...
Awalan adalah karakter Unicode tunggal berkode UTF-8 ...

# build test data
# ===============
  prefix="௵"   # prefix all non-zero length strings will this obvious 3-byte marker.
  prelen=$(echo -n $prefix|wc -c)
  printf \\0 > binfile  # force 1st string to be zero-length (to check zero-length logic) 
  ( lmax=3 # line max ... the last on is set to  255-length (to check  max-length logic)
    for ((i=1;i<=$lmax;i++)) ; do    # add prefixed random length lines 
      suflen=$(numrandom /0..$((255-prelen))/)  # random length string (min of 3 bytes)
      ((i==lmax)) && ((suflen=255-prelen))      # make last line full length (255) 
      strlen=$((prelen+suflen))
      printf \\$((($strlen/64)*100+$strlen%64/8*10+$strlen%8))"$prefix"
      for ((j=0;j<suflen;j++)) ; do
        byteval=$(numrandom /9,10,32..126/)  # output only printabls ASCII characters
        printf \\$((($byteval/64)*100+$byteval%64/8*10+$byteval%8))
      done
        # 'numrandom' is from package 'num-utils"
    done
  ) >>binfile
#
Peter.O
sumber
1
Kode Anda terlihat lebih rumit dari yang seharusnya, terutama generator data uji acak. Anda bisa mendapatkan byte acak dari /dev/urandomsebagian besar unix. Dan data uji acak bukan data uji terbaik, Anda harus memastikan untuk mengatasi kasus-kasus sulit seperti, di sini, karakter nol dan baris baru di tempat batas.
Gilles 'SO- stop being evil'
Ya terima kasih. Saya berpikir untuk menggunakan / dev / acak tetapi mengira gen data uji tidak penting, dan saya ingin menguji drive 'numrandom' (yang Anda sebutkan di tempat lain; 'num-utils'some fitur bagus.). Saya baru saja melihat jawaban Anda lebih dekat, dan menyadari bahwa Anda melakukan hal yang hampir sama, kecuali bahwa itu lebih ringkas :) .. Saya tidak melihat bahwa Anda telah menyatakan poin kunci dalam 3 baris! Saya telah memfokuskan pada referensi bahasa lain Anda . Mendapatkannya untuk bekerja adalah pengalaman yang baik, dan sekarang saya lebih memahami referensi Anda ke bahasa lain! \ x00 bisa menjadi penahan kerang
Peter.O
0

Yang ini hanya menyalin file biner:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
rzr
sumber