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

14

Я амбициозно пытаюсь перевести код C ++ в Bash по множеству причин.

Этот код читает и манипулирует типом файла, специфичным для моего подполя, который написан и полностью структурирован в двоичном виде. Моя первая задача, связанная с двоичными файлами, - скопировать первые 988 байт заголовка, в том виде, в каком они есть, и поместить их в выходной файл, в который я смогу продолжить запись по мере генерирования остальной информации.

Я почти уверен, что мое текущее решение не работает, и реально я не нашел хорошего способа определить это. Так что, даже если это действительно написано правильно, мне нужно знать, как я проверю это, чтобы быть уверенным!

Вот чем я сейчас занимаюсь:

hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}
headInput=`head -c 988 ${inputTrack} | hexdump`
headOutput=`head -c 988 ${output_hdr} | hexdump`
if [ "${headInput}" != "${headOutput}" ]; then echo "output header was not written properly.  exiting.  please troubleshoot."; exit 1; fi

Если я использую hexdump / xxd для проверки этой части файла, хотя я не могу точно прочитать большую ее часть, что-то кажется неправильным. И код, который я написал для сравнения, говорит только, если две строки идентичны, а не если они скопированы так, как я хочу.

Есть ли лучший способ сделать это в Bash? Могу ли я просто скопировать / прочитать двоичные байты в native-binary, чтобы дословно скопировать в файл? (и в идеале хранить как переменные).

neurocoder
источник
Вы можете использовать ddдля копирования отдельных байтов (установив его countв 1). Я не уверен насчет их хранения.
DDPWNAGE
Не делайте bash способом C, это создаст много головных болей. Вместо этого используйте правильные конструкции bash
Ferrybig

Ответы:

22

Работа с двоичными данными на низком уровне в сценариях оболочки, как правило, плохая идея.

bashпеременные не могут содержать байт 0. zshэто единственная оболочка, которая может хранить этот байт в своих переменных.

В любом случае аргументы команды и переменные окружения не могут содержать эти байты, поскольку они являются строками с разделителями NUL, передаваемыми execveсистемному вызову.

Также обратите внимание, что:

var=`cmd`

или его современная форма:

var=$(cmd)

удаляет все завершающие символы новой строки из вывода cmd. Таким образом, если этот двоичный вывод заканчивается в 0xa байтах, он будет искажен при сохранении в $var.

Здесь вам нужно хранить закодированные данные, например, с помощью xxd -p.

hdr_988=$(head -c 988 < "$inputFile" | xxd -p)
printf '%s\n' "$hdr_988" | xxd -p -r > "$output_hdr"

Вы можете определить вспомогательные функции, такие как:

encode() {
  eval "$1"='$(
    shift
    "$@" | xxd -p  -c 0x7fffffff
    exit "${PIPESTATUS[0]}")'
}

decode() {
  printf %s "$1" | xxd -p -r
}

encode var cat /bin/ls &&
  decode "$var" | cmp - /bin/ls && echo OK

xxd -pвывод не занимает мало места, так как он кодирует 1 байт в 2 байта, но он облегчает манипуляции с ним (конкатенация, извлечение частей). base64это тот, который кодирует 3 байта в 4, но с ним не так легко работать.

ksh93Оболочка имеет встроенную команду формат кодирования (использование base64) , которые вы можете использовать свои readи printf/ printкоммунальные услуги:

typeset -b var # marked as "binary"/"base64-encoded"
IFS= read -rn 988 var < input
printf %B var > output

Теперь, если нет транзита через переменные оболочки или env или аргументы команды, вы должны быть в порядке, пока используемые вами утилиты могут обрабатывать любое значение байта. Но обратите внимание, что для текстовых утилит большинство реализаций, не относящихся к GNU, не могут обрабатывать байты NUL, и вы захотите зафиксировать локаль в C, чтобы избежать проблем с многобайтовыми символами. Последний символ, не являющийся символом новой строки, также может вызвать проблемы, а также очень длинные строки (последовательности байтов между двумя байтами 0xa, которые длиннее LINE_MAX).

head -cгде он доступен, здесь должно быть все в порядке, так как он предназначен для работы с байтами и не имеет смысла рассматривать данные как текст. Так

head -c 988 < input > output

должно быть хорошо. На практике, по крайней мере, встроенные реализации GNU, FreeBSD и ksh93 в порядке. POSIX не указывает -cопцию, но говорит, что headдолжен поддерживать строки любой длины (не ограничиваясь LINE_MAX)

С zsh:

IFS= read -rk988 -u0 var < input &&
print -rn -- $var > output

Или:

var=$(head -c 988 < input && echo .) && var=${var%.}
print -rn -- $var > output

Даже zshесли, если он $varсодержит NUL-байты, вы можете передать его как аргумент zshвстроенным (как printописано выше) или функциям, но не как аргументы исполняемым файлам, поскольку аргументы, передаваемые исполняемым файлам, являются строками с разделителями NUL, это ограничение ядра, независимое от оболочки.

Стефан Шазелас
источник
zshэто не единственная оболочка, которая может хранить один или несколько байтов NUL в переменной оболочки. ksh93может сделать это тоже. Внутренне ksh93просто хранит двоичную переменную в виде строки в кодировке base64.
fpmurphy
@ fpmurphy1, это не то, что я называю обработкой двоичных данных , переменная не содержит двоичных данных, поэтому вы не можете использовать для них ни один из операторов оболочки, например, вы не можете передать их встроенным функциям или функциям в своем декодированная форма ... я бы назвал это скорее встроенной поддержкой кодирования / декодирования base64 .
Стефан Шазелас
11

Я амбициозно пытаюсь перевести код C ++ в Bash по множеству причин.

Ну да. Но, возможно, вам следует рассмотреть очень важную причину НЕ делать этого. По сути, «bash» / «sh» / «csh» / «ksh» и тому подобное не предназначены для обработки двоичных данных и не являются большинством стандартных утилит UNIX / LINUX.

Вам лучше либо придерживаться C ++, либо использовать язык сценариев, такой как Python, Ruby или Perl, который способен работать с двоичными данными.

Есть ли лучший способ сделать это в Bash?

Лучше не делать это в bash.

Стивен С
источник
4
+1 за «лучший способ - не делать этого в bash».
Гунтрам Блом поддерживает Монику
1
Другая причина, по которой не следует идти по этому пути, заключается в том, что результирующее приложение будет работать значительно медленнее и потреблять больше системных ресурсов.
fpmurphy
Конвейеры Bash могут выступать в качестве высокоуровневого предметно-ориентированного языка, который может повысить понятность. Там нет ничего о трубопроводе , который не является бинарным, и существуют различные утилиты , реализованные в качестве инструментов командной строки , которые взаимодействуют с двоичными данными ( ffmpeg, imagemagick, dd). Теперь, если кто-то занимается программированием, а не склеивает что-то вместе, тогда лучше использовать полноценный язык программирования.
Att Righ
6

Из вашего вопроса:

скопируйте первые 988 строк заголовка

Если вы копируете 988 строк, то это похоже на текстовый файл, а не на двоичный файл. Однако ваш код, кажется, принимает 988 байтов, а не 988 строк, поэтому я буду считать, что байты верны.

hdr_988=`head -c 988 ${inputFile}`
echo -n "${hdr_988}" > ${output_hdr}

Эта часть может не работать. Во-первых, любые байты NUL в потоке будут удалены, поскольку вы используете ${hdr_988}аргумент командной строки, а аргументы командной строки не могут содержать NUL. Обратные галочки могут также делать пробелы (я не уверен в этом). (На самом деле, поскольку echoэто встроенное, ограничение NUL может не применяться, но я бы сказал, что оно все еще сомнительно.)

Почему бы просто не записать заголовок непосредственно из входного файла в выходной файл, не передав его через переменную оболочки?

head -c 988 "${inputFile}" >"${output_hdr}"

Или, более переносимо,

dd if="${inputFile}" of="${output_hdr}" bs=988 count=1

Поскольку вы упоминаете, что используете bash, а не оболочку POSIX, у вас есть доступная подстановка процессов, так как насчет этого в качестве теста?

cmp <(head -c 988 "${inputFile}") <(head -c 988 "${output_hdr}")

Наконец: рассмотрите возможность использования $( ... )вместо обратных галочек.

Celada
источник
Обратите внимание, что ddэто не обязательно эквивалентно headдля нестандартных файлов. headбудет делать столько read(2)системных вызовов, сколько необходимо, чтобы получить эти 988 байтов, в то время как ddпросто сделает один read(2). У GNU ddесть iflag=fullblockпопытка прочитать этот блок полностью, но тогда он еще менее переносим, ​​чем head -c.
Стефан Шазелас