วิธีใช้ bash script เพื่ออ่านเนื้อหาของไฟล์ไบนารี่


15

ฉันต้องการอ่านตัวอักษรและความยาวคงที่ของสตริง (สตริงไม่สิ้นสุดในไฟล์และความยาวของมันถูกกำหนดโดยตัวอักษรก่อนหน้า)

ฉันจะทำสิ่งนี้ในสคริปต์ทุบตีได้อย่างไร วิธีกำหนดตัวแปรสตริงเพื่อให้ฉันสามารถทำการประมวลผลบางอย่างได้

คำตอบ:


19

หากคุณต้องการใช้ยูทิลิตี้ของเชลล์คุณสามารถใช้headเพื่อแยกจำนวนไบต์และodแปลงไบต์เป็นตัวเลข

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)

อย่างไรก็ตามวิธีนี้ใช้ไม่ได้กับข้อมูลไบนารี มีสองปัญหาคือ

  • คำสั่งเปลี่ยนตัว$(…)แถบบรรทัดใหม่สุดท้ายในการส่งออกคำสั่ง มีวิธีแก้ปัญหาที่ค่อนข้างง่าย: ตรวจสอบให้แน่ใจว่าผลลัพธ์จบลงด้วยอักขระอื่นนอกเหนือจากการขึ้นบรรทัดใหม่จากนั้นดึงอักขระหนึ่งตัวนั้นออก

    string=$(head -c $n; echo .); string=${string%.}
  • ทุบตีเช่นเปลือกหอยส่วนใหญ่จะไม่ดีที่จัดการกับไบต์ null ในฐานะของทุบตี 4.1 ไบต์เป็นโมฆะเพียงจากผลของการทดแทนคำสั่ง ขีดกลาง 0.5.5 และ pdksh 5.2 มีลักษณะการทำงานเหมือนกันและ ATT ksh หยุดอ่านที่ไบต์แรกว่าง โดยทั่วไปแล้วเชลล์และยูทิลิตี้ของมันจะไม่ถูกปรับไปสู่การจัดการกับไฟล์ไบนารี (Zsh เป็นข้อยกเว้นมันถูกออกแบบมาเพื่อรองรับ null ไบต์)

หากคุณมีข้อมูลไบนารีคุณจะต้องเปลี่ยนไปใช้ภาษาเช่น Perl หรือ 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
'

+1 เชลล์สคริปต์ไม่เหมาะสมเสมอ
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 -Nหยุดที่ไบต์ว่างดังนั้นนี่ไม่ใช่วิธีที่เหมาะสมในการทำงานกับข้อมูลไบนารี โดยทั่วไปแล้วเชลล์ที่ไม่ใช่ zsh จะไม่สามารถรับมือกับค่า null ได้
Gilles 'หยุดความชั่วร้าย'

2

หากคุณต้องการจัดการกับไฟล์ไบนารีในเชลล์ตัวเลือกที่ดีที่สุด (เท่านั้น?) คือการทำงานกับเครื่องมือhexdump

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

อ่าน X ไบต์เท่านั้น:

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

อ่านความยาว (และทำงานกับ 0 เป็นความยาว) และจากนั้น "สตริง" เป็นค่าทศนิยมไบต์:

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

คุณสามารถอธิบายสิ่งที่พวกเขาทำและวิธีการทำงานของพวกเขาได้อย่างไร? ตัวเลือกหมายถึงอะไร ผู้ใช้สามารถคาดหวังสิ่งใดจากคำสั่งของคุณ? กรุณาอย่าตอบในความคิดเห็น; แก้ไข  คำตอบของคุณเพื่อให้ชัดเจนและสมบูรณ์ยิ่งขึ้น
G-Man กล่าวว่า 'Reinstate Monica'

2
ฉันสามารถคัดลอก manpages ที่นี่ แต่ฉันไม่เห็นประเด็น มีเพียงคำสั่งพื้นฐานที่ใช้ที่นี่เคล็ดลับเดียวคือการใช้ hexdump
Clément Moulin - SimpleRezo

2
การลงเพราะคุณไม่ชอบ / เข้าใจคำตอบของฉันอย่างจริงจัง
Clément Moulin - SimpleRezo

1

อัพเดท (ด้วยการเข้าใจถึงปัญหาหลังเหตุการณ์): ... คำถาม / คำตอบนี้ (คำตอบของฉัน) ทำให้ฉันคิดถึงสุนัขที่คอยไล่ล่ารถ .. วันหนึ่งในที่สุดเขาก็พบรถ .. โอเคเขาจับได้ แต่ เขาไม่สามารถทำอะไรกับมันได้มากนัก ... อันนี้ 'จับ' สายอักขระ แต่คุณไม่สามารถทำอะไรกับพวกมันได้มากนักถ้าพวกมันฝังตัวเป็นโมฆะไบต์ ... .. ภาษาอื่นอาจอยู่ในลำดับที่นี่)

ddอ่านข้อมูลใด ๆ และทั้งหมด ... แน่นอนว่ามันจะไม่หยุดชะงักที่ศูนย์ในฐานะ "ความยาว" ... แต่ถ้าคุณมี \ x00 ทุกที่ในข้อมูลของคุณคุณจะต้องมีความคิดสร้างสรรค์ในการจัดการมัน ddไม่มีปัญหา แต่เชลล์สคริปต์ของคุณจะมีปัญหา (แต่มันขึ้นอยู่กับสิ่งที่คุณต้องการจะทำกับข้อมูล) ... โดยทั่วไปแล้วผลลัพธ์ต่อไปนี้ "data string" แต่ละตัวไปยังไฟล์ที่มีตัวแบ่งบรรทัดระหว่างแต่ละ strin ...

btw: คุณพูดว่า "character" และฉันคิดว่าคุณหมายถึง "byte" ...
แต่คำว่า "character" นั้นคลุมเครือใน UNICODE ในวันนี้ซึ่งชุดอักขระ ASCII ขนาด 7 บิตใช้เพียงไบต์เดียวต่อตัวอักษร ... และแม้กระทั่งภายในระบบ Unicode จำนวนไบต์จะแตกต่างกันไปขึ้นอยู่กับวิธีการเข้ารหัสอักขระเช่น UTF-8, UTF-16 ฯลฯ

นี่คือสคริปต์ง่าย ๆ เพื่อเน้นความแตกต่างระหว่างข้อความ "อักขระ" และไบต์

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)

หากอักขระความยาวของคุณมีความยาว1 ไบต์และระบุความยาวไบต์สคริปต์นี้ควรทำเคล็ดลับแม้ว่าข้อมูลจะมีอักขระ Unicode ... ddเห็นเฉพาะไบต์โดยไม่คำนึงถึงการตั้งค่าตำแหน่งใด ๆ ...

สคริปต์นี้ใช้ddเพื่ออ่านไฟล์ไบนารีและส่งออกสตริงที่คั่นด้วยตัวคั่น "====" ... ดูสคริปต์ถัดไปสำหรับข้อมูลการทดสอบ

#   
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

ทางออก

สคริปต์นี้สร้างข้อมูลทดสอบซึ่งรวมถึงคำนำหน้า 3 ไบต์ต่อบรรทัด ...
คำนำหน้าเป็นอักขระ Unicode เข้ารหัส 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
#

1
รหัสของคุณดูซับซ้อนกว่าที่ควรเป็นโดยเฉพาะเครื่องกำเนิดข้อมูลการทดสอบแบบสุ่ม คุณสามารถรับจำนวนไบต์แบบสุ่มจากจำนวน/dev/urandomมากได้ และข้อมูลการทดสอบแบบสุ่มไม่ใช่ข้อมูลการทดสอบที่ดีที่สุดคุณควรตรวจสอบให้แน่ใจว่าได้ระบุกรณีที่ยากเช่นที่นี่ตัวอักษรว่างและขึ้นบรรทัดใหม่ในที่ที่มีขอบเขต
Gilles 'หยุดความชั่วร้าย'

ใช่ขอบคุณ. ฉันคิดว่าการใช้ / dev / random แต่คิดว่าการทดสอบข้อมูล gen นั้นไม่มีการนำเข้าที่ยอดเยี่ยมและฉันต้องการทดสอบไดรฟ์ 'numrandom' (ซึ่งคุณพูดถึงที่อื่นนั่นคือ 'คุณสมบัติที่ยอดเยี่ยมของ num-utils) ฉันเพิ่งดูคำตอบของคุณอย่างใกล้ชิดและรู้ว่าคุณกำลังทำสิ่งเดียวกันมากยกเว้นว่ามันสั้นกว่า :) .. ฉันไม่ได้สังเกตว่าคุณได้ระบุประเด็นสำคัญใน 3 บรรทัด! ฉันได้มุ่งเน้นไปที่การอ้างอิงภาษาอื่น ๆของคุณ.. การทำงานให้เป็นประสบการณ์ที่ดีและตอนนี้ฉันเข้าใจการอ้างอิงของคุณกับภาษาอื่น ๆ ได้ดีขึ้น! \ x00 สามารถเป็นตัวหยุดทำงานของเชลล์ได้
Peter.O

0

อันนี้แค่คัดลอกไฟล์ไบนารี:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.