零/空分隔符破坏列命令

零/空分隔符破坏列命令

问题

我想解析一些结构为行(\n分隔)的数据,其中字段由NUL字符分隔\0

许多 Linux 命令使用--zerofor find、 for 或-0for等选项来处理此分隔符xargs,方法是将分隔符定义为\0for 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 作为分隔符,它也会将该输入的每一行转换为输出时的整列。也许您还想使用-tso 来对每行的单个字段进行列化?

可选/额外问题 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(实际上必须是gawkmawk处理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.

相关内容