Як використовувати скрипт bash для читання вмісту бінарних файлів?


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%.}
  • Bash, як і більшість оболонок, погано поводиться з нульовими байтами . Станом на bash 4.1, нульові байти просто випадають з результату підстановки команд. Dash 0.5.5 і pdksh 5.2 мають однакову поведінку, і ATT ksh перестає читати на першому нульовому байті. Як правило, оболонки та їх утиліти не спрямовані на справу з бінарними файлами. (Zsh - виняток, він призначений для підтримки нульових байтів.)

Якщо у вас є двійкові дані, ви хочете перейти на таку мову, як 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, не справляються з нулями.
Жил "ТАК - перестань бути злим"

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 каже: "Відновіть Моніку"

2
Ну, я можу скопіювати сюди вкладки, але я не бачу сенсу. Тут використовуються лише основні команди, єдиний фокус - використання hexdump.
Клемент Мулен - SimpleRezo

2
Відмовляюся від того, що твоя відповідь не подобається / розумієш серйозно?
Клемент Мулен - SimpleRezo

1

ОНОВЛЕННЯ (заднім числом): ... Це питання / відповідь (моя відповідь) змушує мене думати про собаку, яка постійно переслідує машину .. Одного разу, нарешті, він підходить до машини .. Гаразд, він її спіймав, але він насправді не може з цим багато зробити ... Цей ансер 'ловить' рядки, але тоді ви не можете багато зробити з ними, якщо вони вбудували нульові байти ... (такий великий +1 відповіді Гіллу .. інша мова може бути тут порядком.)

ddчитає будь-які дані ... Це, звичайно, не пробиватиметься на нулі як "довжина" ... але якщо у вас є \ x00 де-небудь у ваших даних, вам потрібно буде бути творчим, як ви їх обробляєте; ddне має проблем із цим, але ваш сценарій оболонки матиме проблеми (але це залежить від того, що ви хочете зробити з даними) ... Далі нижче в основному виводиться кожен "рядок даних" у файл із роздільником рядків між кожною строкою ...

btw: Ви говорите "персонаж", і я вважаю, що ви маєте на увазі "байт" ...
але слово "характер" стало неоднозначним в ці дні UNICODE, де лише 7-бітний набір символів ASCII використовує один байт на символ ... І навіть у системі 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-байтний префікс на рядок ...
Префікс - це єдиний UTF-8 кодований символ Unicode ...

# 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більшості одинаків. І випадкові дані тесту не є найкращими тестовими даними, ви повинні переконатися у вирішенні складних випадків, таких як, наприклад, нульові символи та новий рядок у граничних місцях.
Жиль "ТАК - перестань бути злим"

Так дякую. Я думав використовувати / dev / random, але порахував, що тестові дані не мають великого значення, і я хотів перевірити диск "numrandom" (що ви згадали в іншому місці; 'num-utils's nice nice.). Я щойно ознайомився з вашою відповіддю і зрозумів, що ви робите майже те ж саме, за винятком того, що це більш лаконічно :) .. Я не помітив, що ви вказали ключові моменти у 3 рядках! Я зосередився на ваших іншомовних посиланнях. Налагодження роботи було гарним досвідом, і тепер я краще розумію ваші посилання на інші мови! \ x00 може бути стопором
Peter.O

0

Це просто скопіюйте двійковий файл:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.