Ответы:
Если вы хотите придерживаться утилит оболочки, вы можете использовать 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
'
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
read -N
останавливается на нулевых байтах, так что это не подходящий способ работы с двоичными данными. В общем случае оболочки, отличные от zsh, не справляются с нулями.
Если вы хотите иметь дело с двоичным файлом в оболочке, лучший вариант (только?) - это работать с инструментом 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
ОБНОВЛЕНИЕ (задним числом): ... Этот вопрос / ответ (мой ответ) заставляет меня думать о собаке, которая продолжает преследовать машину .. Однажды, наконец, он догоняет машину .. Хорошо, он поймал ее, но он действительно ничего не может с этим поделать ... Этот ансер "ловит" строки, но тогда вы не сможете с ними многое сделать, если они имеют встроенные нулевые байты ... (так что большой ответ +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
#
/dev/urandom
большинства юнитов. А данные случайных тестов не самые лучшие тестовые данные, вы должны убедиться, что решаете сложные случаи, такие как, здесь, нулевые символы и символ новой строки в граничных местах.