Làm thế nào để sử dụng tập lệnh bash để đọc nội dung tệp nhị phân?


14

Tôi muốn đọc một ký tự và sau đó là một chuỗi có độ dài cố định (chuỗi không được kết thúc bằng null trong tệp và độ dài của nó được cho bởi ký tự trước).

Làm thế nào tôi có thể làm điều này trong một kịch bản bash? Làm thế nào để xác định biến chuỗi để tôi có thể thực hiện một số xử lý hậu kỳ trên nó?

Câu trả lời:


19

Nếu bạn muốn gắn bó với các tiện ích shell, bạn có thể sử dụng headđể trích xuất một số byte và odđể chuyển đổi một byte thành một số.

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)

Tuy nhiên, điều này không làm việc cho dữ liệu nhị phân. Có hai vấn đề:

  • Lệnh thay thế $(…)dải mới dòng cuối cùng trong đầu ra lệnh. Có một cách giải quyết khá dễ dàng: đảm bảo đầu ra kết thúc bằng một ký tự khác với một dòng mới, sau đó loại bỏ một ký tự đó.

    string=$(head -c $n; echo .); string=${string%.}
  • Bash, giống như hầu hết các shell, rất tệ trong việc xử lý các byte rỗng . Kể từ bash 4.1, các byte null chỉ đơn giản được loại bỏ khỏi kết quả của việc thay thế lệnh. Dash 0,5.5 và pdksh 5.2 có cùng hành vi và ATT ksh dừng đọc ở byte null đầu tiên. Nói chung, shell và các tiện ích của chúng không hướng đến việc xử lý các tệp nhị phân. (Zsh là ngoại lệ, nó được thiết kế để hỗ trợ byte rỗng.)

Nếu bạn có dữ liệu nhị phân, bạn sẽ muốn chuyển sang ngôn ngữ như Perl hoặc 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
'

Các tập lệnh shell +1 không phải lúc nào cũng phù hợp
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

5
read -Ndừng ở byte rỗng, vì vậy đây không phải là cách phù hợp để làm việc với dữ liệu nhị phân. Nói chung, các shell khác ngoài zsh không thể đối phó với null.
Gilles 'SO- ngừng trở nên xấu xa'

2

Nếu bạn muốn có thể xử lý tệp nhị phân trong shell, tùy chọn tốt nhất (chỉ?) Là làm việc với công cụ hexdump .

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

Chỉ đọc X byte:

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

Đọc độ dài (và làm việc với 0 là độ dài) và sau đó "chuỗi" dưới dạng giá trị thập phân 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

Thay vì chỉ trình bày một loạt các lệnh, bạn có thể giải thích những gì họ làm và cách họ làm việc không? Các tùy chọn có nghĩa là gì? Những gì người dùng có thể mong đợi từ các lệnh của bạn? Xin vui lòng không trả lời trong các ý kiến; chỉnh sửa  câu trả lời của bạn để làm cho nó rõ ràng và đầy đủ hơn.
G-Man nói 'Phục hồi Monica'

2
Chà, tôi có thể sao chép các trang ở đây, nhưng tôi không thấy vấn đề. Chỉ có các lệnh cơ bản được sử dụng ở đây, mẹo duy nhất là sử dụng hexdump.
Clément Moulin - SimpleRezo

2
Nghiêm túc vì bạn không thích / hiểu câu trả lời của tôi?
Clément Moulin - SimpleRezo

1

CẬP NHẬT (với nhận thức muộn màng): ... Câu hỏi / câu trả lời này (câu trả lời của tôi) khiến tôi nghĩ về con chó cứ đuổi theo xe .. Một ngày nọ, cuối cùng, anh ta đuổi kịp xe .. Được rồi, anh ta đã bắt được nó, nhưng anh ta thực sự không thể làm gì nhiều với nó ... Điều này anser 'bắt' các chuỗi, nhưng sau đó bạn không thể làm gì nhiều với chúng, nếu chúng đã nhúng null-byte ... (vì vậy câu trả lời +1 lớn cho Gilles .. một ngôn ngữ khác có thể theo thứ tự ở đây.)

ddđọc bất kỳ và tất cả dữ liệu ... Nó chắc chắn sẽ không bực bội ở mức 0 dưới dạng "độ dài" ... nhưng nếu bạn có \ x00 ở bất kỳ đâu trong dữ liệu của mình, bạn sẽ cần sáng tạo cách bạn xử lý nó; ddkhông có biểu tượng nào với nó, nhưng tập lệnh shell của bạn sẽ có vấn đề (nhưng nó phụ thuộc vào những gì bạn muốn làm với dữ liệu) ... Về cơ bản, sau đây xuất từng "chuỗi dữ liệu", thành một tệp có một vạch chia giữa mỗi strin ...

btw: Bạn nói "ký tự" và tôi giả sử bạn có nghĩa là "byte" ...
nhưng từ "ký tự" đã trở nên mơ hồ trong những ngày này của UNICODE, trong đó chỉ có bộ ký tự ASCII 7 bit sử dụng một byte cho mỗi ký tự ... Và ngay cả trong hệ thống Unicode, số byte thay đổi tùy theo phương pháp mã hóa ký tự , vd. UTF-8, UTF-16, v.v.

Đây là một tập lệnh đơn giản để làm nổi bật sự khác biệt giữa "ký tự" văn bản và 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)

Nếu ký tự độ dài của bạn dài 1 byte và biểu thị độ dài byte , thì tập lệnh này sẽ thực hiện thủ thuật, ngay cả khi dữ liệu chứa các ký tự Unicode ... ddchỉ nhìn thấy các byte bất kể cài đặt ngôn ngữ địa phương nào ...

Tập lệnh này sử dụng ddđể đọc tệp nhị phân và xuất ra các chuỗi được phân tách bằng dấu chia "====" ... Xem tập lệnh tiếp theo để biết dữ liệu thử nghiệm

#   
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

lối ra

Tập lệnh này xây dựng dữ liệu thử nghiệm bao gồm tiền tố 3 byte trên mỗi dòng ...
Tiền tố là một ký tự Unicode được mã hóa UTF-8 duy nhất ...

# 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
#

1
Mã của bạn trông phức tạp hơn mức cần thiết, đặc biệt là trình tạo dữ liệu thử nghiệm ngẫu nhiên. Bạn có thể nhận được các byte ngẫu nhiên từ /dev/urandomhầu hết các thông báo. Và dữ liệu thử nghiệm ngẫu nhiên không phải là dữ liệu thử nghiệm tốt nhất, bạn nên đảm bảo giải quyết các trường hợp khó khăn như, ở đây, ký tự null và dòng mới ở các vị trí biên.
Gilles 'SO- ngừng trở nên xấu xa'

Vâng cảm ơn. Tôi đã nghĩ đến việc sử dụng / dev / ngẫu nhiên nhưng hình dung gen dữ liệu thử nghiệm không được nhập nhiều và tôi muốn lái thử 'numrandom' (trong đó bạn đã đề cập ở nơi khác; 'các tính năng hay của num-utils.). Tôi vừa xem kỹ câu trả lời của bạn, và nhận ra rằng bạn đang làm khá nhiều điều tương tự, ngoại trừ việc nó ngắn gọn hơn :) .. Tôi đã không nhận thấy rằng bạn đã nêu những điểm chính trong 3 dòng! Tôi đã tập trung vào các tài liệu tham khảo ngôn ngữ khác của bạn .. Làm cho nó hoạt động là một trải nghiệm tốt và bây giờ tôi hiểu rõ hơn các tài liệu tham khảo của bạn cho các ngôn ngữ khác! \ x00 có thể là công cụ chặn vỏ
Peter.O

0

Cái này chỉ cần sao chép một tệp nhị phân:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.