Как я могу обработать необработанные двоичные данные в канале bash?

15

У меня есть функция bash, которая принимает файл в качестве параметра, проверяет, существует ли файл, а затем записывает все, что выходит из stdin, в файл. Наивное решение прекрасно работает для текста, но у меня проблемы с произвольными двоичными данными.

echo -n '' >| "$file" #Truncate the file
while read lines
do  # Is there a better way to do this? I would like one...
    echo $lines >> "$file"
done
Дэвид Соутер
источник

Ответы:

15

Ваш способ заключается в добавлении разрывов строк к каждой вещи, которую он записывает в пространстве любого разделителя ( $IFS), который используется для разделения чтения. Вместо того, чтобы разбивать его на новые строки, просто возьмите все это и передайте. Вы можете уменьшить весь приведенный выше код до следующего:

 cat - > $file

Вам не нужен усеченный бит, он будет усекать и записывать в него весь поток STDIN.

Изменить: Если вы используете Zsh вы можете просто использовать > $fileвместо кошки. Вы перенаправляете файл и усекаете его, но если там что-то висит, ожидая, что что-то примет STDIN, оно будет прочитано в этот момент. Я думаю, что вы можете сделать что-то подобное с Bash, но вам придется установить специальный режим.

Калеб
источник
Я не смог заставить работать пример перенаправления stdin, но изменил пример cat на> | (У меня есть набор noclobber) работает как шарм. Спасибо, что сделали мой день ^. ^
Дэвид Саутер
+1 для версии без кошки. Всегда избегайте бесполезных кошек;)
rozcietrzewiacz
@rozcietrzewiacz: Правда, за исключением того, что это была запоздалая мысль, и я был неправ. Это не может быть бесполезным использованием кошки. Единственное , что вы могли бы быть в состоянии сделать это > $file. Это работает только как первое, что ищет stdin в родительском сценарии оболочки. По сути, весь код Дэвида может быть сведен к одному символу, но я думаю, что cat -он более элегантен и менее проблематичен, потому что он понятен с первого взгляда.
Калеб
Иногда я catнатягиваю четыре или пять секунд вместе, чтобы разозлить фанатиков UUOC
Майкл
@MichaelMrozek: Иногда я называю свои файлы данных catпросто так, чтобы люди, которые настаивают на их использовании, обязательно должны были делать умственную гимнастику, чтобы прочитать код. Именованные каналы также являются хорошими целями.
Калеб
7

Для того, чтобы прочитать текстовый файл в буквальном смысле, не используйте обычный read, который обрабатывает выход двумя способами:

  • readинтерпретирует \как побег персонажа; используйте, read -rчтобы отключить это.
  • readразбивается на слова по символам в $IFS; установите IFSпустую строку, чтобы отключить это.

Обычная идиома для обработки текстового файла построчно

while IFS= read -r line; do 

Для объяснения этой идиомы см. Почему while IFS= readиспользуется так часто, а не IFS=; while read..? ,

Чтобы записать строку буквально, не просто используйте plain echo, который обрабатывает строку двумя способами:

  • На некоторых оболочках происходит echoобратная косая черта. (На bash это зависит от того, установлена ​​ли xpg_echoопция.)
  • Несколько строк обрабатываются как опции, например, -nили -e(точный набор зависит от оболочки).

Портативный способ печати строки буквально с printf. (В bash нет лучшего способа, если только вы не знаете, что ваш ввод не выглядит как вариант echo.) Используйте первую форму, чтобы напечатать точную строку, и вторую форму, если вы хотите добавить новую строку.

printf %s "$line"
printf '%s\n' "$line"

Это подходит только для обработки текста , потому что:

  • Большинство оболочек будут подавлены нулевыми символами на входе.
  • Когда вы прочитали последнюю строку, у вас нет возможности узнать, был ли перевод строки в конце или нет. (У некоторых старых оболочек могут возникнуть большие проблемы, если ввод не заканчивается новой строкой.)

Вы не можете обрабатывать двоичные данные в оболочке, но современные версии утилит в большинстве устройств могут справиться с произвольными данными. Чтобы передать весь ввод на выход, используйте cat. Переход по касательной echo -n ''- сложный и непереносимый способ ничего не делать; echo -nбудет таким же хорошим (или не зависящим от оболочки), :более простым и полностью переносимым.

: >| "$file"
cat >>"$file"

или, проще,

cat >|"$file"

В сценарии вам обычно не нужно использовать, >|так noclobberкак по умолчанию он выключен.

Жиль "ТАК - перестань быть злым"
источник
спасибо за то, что указали на xpg_echo, на самом деле это проблема, с которой я столкнулся где-то еще в своем коде и даже не осознавал Re noclobber, я имею обыкновение включать его в моем bashrc.
Дэвид Саутер
0

Это будет делать именно то, что вы хотите:

( while read -r -d '' ; do
    printf %s'\0' "${REPLY}" ;
  done ;

  # When read hits EOF, it returns non-zero which exits the while loop.
  # That data still needs to be output:
  printf %s "${REPLY}"
) >> ${file}

Обратите внимание на использование памяти, хотя. Это читает входные данные в нулевой форме.

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

Относительно вашего усеченного шага:

echo -n '' >| "$file" #Truncate the file

гораздо проще и эквивалентнее:

> ${file}   #Truncate the file
Марк Тамский
источник