Вопрос или проблема
Я могу заставить 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
на обработку текста с нулевыми байтами, что позволяет работать с более универсальными форматами данных.