问题
我想解析一些结构为行(\n
分隔)的数据,其中字段由NUL
字符分隔\0
。
许多 Linux 命令使用--zero
for find
、 for 或-0
for等选项来处理此分隔符xargs
,方法是将分隔符定义为\0
for gawk
。
我无法理解如何将column
解释NUL
作为分隔符。
例子
如果生成以下数据集(2行3列,用 分隔\0
):
echo -e "line1\nline2" | awk 'BEGIN {OFS="\0"} {print $1"columnA",$1"columnB",$1"columnC"}'
您将得到预期的以下输出(\0
不会显示分隔符,但分隔每个字段):
line1columnAline1columnBline1columnC
line2columnAline2columnBline2columnC
但是,当我尝试使用 column 来显示我的列时,尽管传递了\0
,但由于某种原因,输出仅显示第一列:
echo -e "line1\nline2" \ | awk 'BEGIN {FS="\0"; OFS="\0"} {print $1"columnA",$1"columnB",$1"columnC"}' | column -s '\0'
line1columnA line2columnA
实际上,即使不提供分隔符,列似乎也会在 nul 字符处中断:
echo -e "line1\nline2" \ | awk 'BEGIN {FS="\0"; OFS="\0"} {print $1"columnA",$1"columnB",$1"columnC"}' | column
line1columnA line2columnA
问题
- 有没有办法用作
\0
字段/列分隔符column
? - 可选/额外问题:为什么列的行为像这样(我希望
\0
如果不进行管理,将完全忽略该列,并将整行打印为单个字段)? - 可选/奖励问题 2:这些列中的一些数据将是文件路径,我想将其用作
\0
最佳实践。您是否有更好的做法建议在文件中存储“随机字符串”,而不必转义它们可能包含的潜在冲突字段分隔符?
答案1
有没有办法使用 \0 作为列中的字段/列分隔符?
column
不,因为(据我所知)的两个实现都是历史上的BSD和其中的一个util-linux 包,两者都使用标准 C 库的字符串操作函数来解析输入行,并且这些函数在字符串以 NUL 结尾的假设下工作。换句话说,NUL 字节意味着总是标记结尾的任何细绳。
可选/额外问题:为什么列的行为像这样(如果不管理,我希望 \0 被完全忽略,并且整行被打印为单个字段)?
除了我上面解释的内容之外,请注意该选项-s
期望文字人物。它不是解析转义语法\0
(也不\n
重要)。这意味着你告诉column
将 a\
和 a0
视为有效分隔符为其输入。
$''
如果您使用的是支持它的众多 shell 之一(例如,它在 中可用,bash
但在 中不可用dash
),则可以通过字符串语法提供转义序列。因此,如果由这些 shell 之一运行,则例如column -s $'\n'
将是有效的(指定 <newline> 作为列分隔符)。
顺便说一句,我不清楚您的期望是什么column
。即使它确实支持 NUL 作为分隔符,它也会将该输入的每一行转换为输出时的整列。也许您还想使用-t
so 来对每行的单个字段进行列化?
可选/额外问题 2:这些列中的一些数据将是文件路径,我想使用 \0 作为最佳实践。您是否有更好的做法建议在文件中存储“随机字符串”,而不必转义它们可能包含的潜在冲突字段分隔符?
我所知道的唯一一种方法是为每个字段添加其长度前缀,以文本或二进制形式表示(如您认为合适)。但你肯定无法将它们通过管道传输到column
.
另外,如果您关心的是文件路径,那么您应该考虑不是使用\n
任一作为“结构”分隔符,因为这是文件名的完全有效的字符。
就像概念验证一样,基于您的示例,但使用 NUL 作为结构/记录分隔符和长度指定的字段:(我还对您的示例字符串进行了一些修改以涉及多字节字符)
echo -e 'line1\nline2 ò' \ | LC_ALL=C awk '
BEGIN {
ORS="\0"
# here we just move arguments away from ARGV
# so that awk reads input from stdin
for (i in ARGV) {
c[i]=ARGV[i]
delete ARGV[i]
}
}
{
# first field is the line read
printf "%4.4d%s", length, $0
# then a field for each argument
for(i=1; i<length(c); i++)
printf "%4.4d%s", length(c[i]), c[i]
printf "%s", ORS
}
' "€ column A" $'colu\nmnB' "column C"
使用论点toawk
传递任意数量的任意列字符串。
然后,假设的对应脚本awk
(实际上必须是gawk
或mawk
处理RS="\0"
):
LC_ALL=C awk '
BEGIN { RS="\0" }
{
nf=0; while(length) {
field_length = substr($0, 1, 4)
printf "field %d: \"%s\""ORS, ++nf, substr($0, 5, field_length)
$0 = substr($0, 5+field_length)
}
printf "%s", ORS
}
'
请注意,指定相同的两个脚本的区域设置以匹配字符大小。指定LC_ALL=C
两者都很好。
答案2
您的列甚至没有到达您的 awk 命令。即使在 echo 命令之前,第一个零之后的所有内容都会丢失。您不能在变量中存储二进制零。
var=$'zzz\x00zzz'
echo "${#var}"
3
var=$'zzz\xFFzzz'
echo "${#var}"
7
在开始执行您计划执行的操作之前,您可以将tr
所有零更改为您选择的任何其他分隔符。
或者你可以将你的 shell 更改为zsh
.