Как использовать скрипт bash для чтения содержимого двоичного файла?

15

Я хочу прочитать символ, а затем фиксированную длину строки (строка не заканчивается нулем в файле, а ее длина задается предыдущим символом).

Как я могу сделать это в скрипте bash? Как определить строковую переменную, чтобы я мог выполнить некоторую постобработку на ней?

Аманда
источник

Ответы:

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
Клемент Мулен - SimpleRezo
источник
Вместо того, чтобы просто представить несколько команд, вы можете объяснить, что они делают и как они работают? Что означают варианты? Какой вывод пользователь может ожидать от ваших команд? Пожалуйста, не отвечайте в комментариях; отредактируйте  свой ответ, чтобы сделать его более понятным и полным.
G-Man говорит: «Восстановите Монику»
2
Ну, я могу скопировать man-страницы здесь, но я не вижу смысла. Здесь используются только базовые команды, единственная хитрость - использование hexdump.
Клемент Мулен - SimpleRezo
2
Даунтинг, потому что ты не любишь / не понимаешь мой ответ, серьезно?
Клемент Мулен - SimpleRezo
1

ОБНОВЛЕНИЕ (задним числом): ... Этот вопрос / ответ (мой ответ) заставляет меня думать о собаке, которая продолжает преследовать машину .. Однажды, наконец, он догоняет машину .. Хорошо, он поймал ее, но он действительно ничего не может с этим поделать ... Этот ансер "ловит" строки, но тогда вы не сможете с ними многое сделать, если они имеют встроенные нулевые байты ... (так что большой ответ +1 для Жиля .. другой язык может быть в порядке здесь.)

ddчитает любые и все данные ... Это, конечно, не будет показывать ноль как "длину" ... но если у вас есть \ x00 где-нибудь в ваших данных, вам нужно проявить изобретательность в том, как вы справляетесь с этим; ddне имеет никаких проблем с этим, но у вашего сценария оболочки будут проблемы (но это зависит от того, что вы хотите сделать с данными) ... Следующее в основном выводит каждую «строку данных» в файл с разделителем строк между каждой строкой ...

Кстати: вы говорите «символ», и я предполагаю, что вы имеете в виду «байт» ...
но слово «символ» стало двусмысленным в наши дни 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 байт и указывает длину в байтах , то этот сценарий должен справиться с задачей, даже если данные содержат символы Юникода ... 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
#
Peter.O
источник
1
Ваш код выглядит сложнее, чем должно быть, особенно генератор случайных тестовых данных. Вы можете получить случайные байты от /dev/urandomбольшинства юнитов. А данные случайных тестов не самые лучшие тестовые данные, вы должны убедиться, что решаете сложные случаи, такие как, здесь, нулевые символы и символ новой строки в граничных местах.
Жиль "ТАК - перестать быть злым"
Да, спасибо. Я подумал об использовании / dev / random, но подумал, что генерация тестовых данных не имеет большого значения, и я хотел протестировать накопитель 'numrandom' (о котором вы упоминали в другом месте; некоторые замечательные функции num-utils). Я просто внимательно посмотрел на ваш ответ и понял, что вы делаете почти то же самое, за исключением того, что он более лаконичен :) .. Я не заметил, что вы изложили ключевые моменты в 3 строки! Я сфокусировался на ваших ссылках на других языках . Начало работы было хорошим опытом, и теперь я лучше понимаю ваши ссылки на другие языки! \ x00 может быть пробкой-оболочкой
Peter.O
0

Это просто скопировать двоичный файл:

 while read -n 1 byte ; do printf "%b" "$byte" ; done < "$input" > "$output"
RZR
источник