Як я можу працювати з бінарним в 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, щоб перевірити цю частину файлу, хоча я не можу точно прочитати більшість із них, щось здається не так. І код, який я написав для порівняння, говорить лише мені, якщо два рядки однакові, а не якщо вони скопійовані так, як я хочу, щоб вони були.

Чи є кращий спосіб зробити це в башті? Чи можу я просто скопіювати / прочитати бінарні байти в нативно-бінарний файл, щоб скопіювати його у дослівний файл? (а в ідеалі також зберігати як змінні).


Ви можете використовувати ddдля копіювання окремих байтів (встановивши його countна 1). Я не впевнений у їх зберіганні.
DDPWNAGE

Не бійся на C, це створить багато головних болів. Замість цього використовуйте належні конструкції bash
Ferrybig

Відповіді:


22

Робота з бінарними даними на низькому рівні в скриптах оболонки, як правило, погана ідея.

bashзмінні не можуть містити байт 0. zshЄдина оболонка, яка може зберігати цей байт у своїх змінних.

У будь-якому випадку аргументи команд та змінні середовища не можуть містити ці байти, оскільки вони є рядками з обмеженою NUL, переданими в execveсистемний виклик.

Також зауважте, що:

var=`cmd`

або його сучасна форма:

var=$(cmd)

знімає всі знаки нового рядка з виводу cmd. Отже, якщо цей двійковий вихід закінчується в 0x байтах, він буде керований, коли зберігається в $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, яка здатна мати справу з бінарними даними.

Чи є кращий спосіб зробити це в башті?

Кращий спосіб - не робити цього в баш.


4
+1 за "Кращий спосіб - це не робити це в баш".
Гунтрам Блом підтримує Моніку

1
Ще одна причина не йти цим маршрутом - це те, що результуюча програма буде працювати значно повільніше та споживати більше системних ресурсів.
fpmurphy

Баш-конвеєри можуть виступати як різновидова доменна мова високого рівня, що може підвищити зрозумілість. Там немає нічого про трубопровід , який не є бінарним, і існують різні утиліти , реалізовані в якості інструментів командного рядка , які взаємодіють з двійковими даними ( 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 може не застосовуватися, але я б сказав, що це все ще iffy.)

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

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}")

Нарешті: подумайте про використання $( ... )замість підказок.


Зауважте, що ddце не обов'язково еквівалентно headдля нестандартних файлів. headробитиме , як багато read(2)системні виклики по мірі необхідності , щоб отримати ці 988 байт , а ddпросто зробити один read(2). GNU ddмає iflag=fullblockспробувати прочитати цей блок повністю, але це тоді навіть менш портативно, ніж head -c.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.