Команда column не может обработать нулевой разделитель.

Вопрос или проблема

Я могу заставить column использовать табуляцию в качестве разделителя полей:

$ printf "a\tb\tc\n" | column -s $'\t' -t
a  b  c

Но использование нулевого разделителя, похоже, не работает:

$ printf "a\0b\0c\n" | column -s $'\0' -t
column: line too long

Чтобы подтвердить, что нулевые разделители действительно есть:

$ printf "a\0b\0c\n" | xxd
00000000: 6100 6200 630a                           a.b.c.

Что здесь может быть не так?

Никакая выполняемая команда не может иметь NUL в своих аргументах, так как в системном вызове execve() аргументы командной строки являются строками, разделёнными NUL. $'\0' работает только в оболочке zsh и в аргументах к встроенным командам или функциям (где execve() не используется).

$ zsh
% printf %s $'a\0b' | od -tc       # встроенный printf
0000000   a  \0   b
0000003
% /bin/printf %s $'a\0b' | od -tc  # внешний printf
0000000   a
0000001

В других оболочках, которые поддерживают операторы квотирования $'...' из ksh93, что находится после \0 (или \u0000, \x0, x{0}, \c@ или \C-@, которые представляют другие возможные представления NUL в зависимости от оболочки) внутри просто отбрасывается ($'a\0b'$'c\0d' то же самое, что и ac)¹ ². В любом случае, external-command $'something\0' – это то же самое, что и external-command something, а external-command $'\0' то же самое, что и external-command '' во всех оболочках.

Чтобы передать NUL команде, необходимо сделать это другим способом, используя какую-либо форму соглашения. Например, это может быть \0 буквально (как в awk -v nul="\0" или в аргументе формата printf) или %00 (как в URI), см. также '', что интерпретируется как NUL в read -d в bash, zsh или в более поздних версиях ksh93 или cut -d GNU.

column, найденный в util-linux (основанный на соответствующей BSD-команде), не поддерживает ни одно из этого и, как и большинство традиционных утилит (в отличие от большинства утилит GNU), даже не поддерживает NUL в своём вводе, так как работает с нулевыми строками (C-строками) внутри.

$ printf '%b\n' 'a\0b c' 'aaaaaaaaaax d' | column -t
a
aaaaaaaaaax  d

b c исчезло.

Чтобы обойти эту проблему, вы можете заменить байт 0 на байт 1 (или любую другую байтовую величину, не встречающуюся во вводе) перед передачей в column и заменить обратно после:

$ printf '%b\n' 'a b\0c' 'aaaa bbbb\0d' |
  tr '\0' '\1' |
  column -ts $'\1' |
  tr '\1' '\0'
a b        c
aaaa bbbb  d

¹ $'...' был добавлен в издание 2024 года спецификации POSIX языка sh, и оба поведения разрешены.

² fish и es — это две (не POSIX-подобные) оболочки, которые поддерживают последовательности символов \ooo (также \xhh для es) за пределами кавычек, и они ведут себя аналогично, за исключением того, что последние версии es выдают ошибку bad backslash escape для \0 или \x0.

Ответ или решение

Теория

Команда column из пакета util-linux предназначена для форматирования текста в виде таблицы с учетом заданного разделителя. Однако, как и многие традиционные утилиты на базе BSD, она не поддерживает обработку нулевых байтов (NUL) в качестве разделителей. Дело в том, что внутри утилита работает с C-строками, которые завершаются символом NUL, и из-за этого ей не удается корректно понять, где находятся границы полей, если нулевой байт используется как разделитель.

Системный вызов execve(), который используется при выполнении большинства команд в Unix-подобных системах, также не позволяет передавать нулевые байты в аргументах, так как они трактуются как конец строки.

Пример

Вы попытались использовать команду column, чтобы разделять поля при помощи нулевого байта, но столкнулись с ошибкой:

printf "a\0b\0c\n" | column -s $'\0' -t

Это приводит к сообщению об ошибке: column: line too long. Это происходит потому, что column ожидает разделителей и строк, которые не содержат нулевые байты.

Применение

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

printf '%b\n' 'a\0b\0c\n' | tr '\0' '\1' | column -s $'\1' -t | tr '\1' '\0'

В этом примере нулевые байты заменяются на \1, обрабатываются утилитой column, затем заменяются обратно на \0.

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

Оцените материал
Добавить комментарий

Капча загружается...