如何在 sed 替换和删除命令中使用 NUL 字符作为分隔符?

如何在 sed 替换和删除命令中使用 NUL 字符作为分隔符?

这是我在打算替换/path/to/a/path/to/busingNUL作为分隔符/定界符时尝试的方法:

$ cat pathsList| sed -r -e 's\0/path/to/a\0/path/to/b\0g'
sed: -e expression #1, char 27: number option to `s' command may not be zero

我想去的地方NUL NUL/是唯一不允许的字符ext4fs,并且/已经被大量用作路径名分隔符。另外,我想避免仅仅为了能够使用而引用和取消引用我的数据sed

如果NUL不能用作分隔符(比方说),我可以接受任何比引用和取消引用我的数据更好的解决方法。

$ sed --version
sed (GNU sed) 4.4

答案1

不幸的是,似乎不可能s///在 sed 中使用 NUL 作为命令的分隔符。

如果你想创建一个包含 NUL 字符的字符串,你可以使用$'...'bash 和其他 shell 识别的形式,所以你可能认为这可行:

sed -r -e $'s\0o\0x\0g'

但是 Linux(以及一般的 Unix)中传递参数的方式使得实际上不可能传递带有嵌入 NUL 的字符串,因为你得到的只是 argc (参数数量)和 argv ,它是一个数组char *,然后以 NUL 结尾的字符串(C 字符串)是获取参数的唯一可能的方式。换句话说,所有 sed (或任何程序)都会查看传递的是否$'s\0o\0x\0g'是简单的"s"(以及 NUL,它们必须将其视为字符串的结尾。)

我想也许将其作为外部文件传递给 sed 可能会起作用,因为在这种情况下 sed 可以知道 NUL 已嵌入并可能通过其长度跟踪完整字符串,所以我尝试了以下方法:

$ cat -v script.sed 
s^@o^@x^@g

s^@是 NUL 字节。我使用(三个零)将它们插入到 vim 中,Ctrlv000这是通过 ASCII 值输入字符的 vim 击键。

但这似乎也不起作用:

$ echo "/path/to/a/folder" | sed -r -f script.sed 
sed: file script.sed line 1: delimiter character is not a single-byte character

s有趣的是,这与脚本文件中只有一个的情况不同,在这种情况下 sed 抱怨unterminated 's' command...所以它似乎通过字符串的长度来跟踪字符串,但看起来仍然不高兴使用 NUL 作为它的分隔符。

查看 的源代码sed,不清楚这是有意为之还是一个错误。在is_mb_char()尝试检测字节是否是多字节字符一部分的函数中,处理 NUL像这样:

case 0: /* Special case of mbrtowc(3): the NUL character */
  /* TODO: test this */
  return 1;

在这种情况下,return 1意味着“是的,它是一个多字节字符”,但事实并非如此。

上面几行的评论说:

/*
 * Return zero in all other cases:
 *   CH is a valid single-byte character (e.g. 0x01-0x7F in UTF-8 locales);
 *   CH is an invalid byte in a multibyte sequence for the currentl locale,
 *   CH is the NUL byte.
 */

那么也许return 0是故意的?

犯罪引入这段代码的上下文没有更多的上下文......

手册页mbrtowc(3)提到L'\0'我认为这是某种多字节 NUL,所以也许这就是他们决定以这种方式处理它的原因?

我希望这些信息仍然有帮助!

答案2

虽然 NUL 无法在文件名中找到(出于类似的原因,它无法在命令参数中找到),.(非常常见),^, *, [, $\所有这些都可以并且也必须按原样进行转义sed的命令理解的正则表达式运算符s

你总是可以这样做逃跑以自动化的方式

请注意,除了 NUL 之外,换行符和所有多字节字符也不能在 GNU 中使用sed。其他实现可能有不同的限制。 POSIX 还禁止反斜杠(尽管它适用于 GNU sed),因此我建议坚持使用可移植字符集中除反斜杠之外的图形字符。

答案3

如果要将单个字符(字节)替换为单个字符(字节),请使用tr

$ echo "/path/to/a/folder" | tr ao xy
/pxth/ty/x/fylder

对于任意字符串,您可以使用 Perl:

$ echo "/path/to/a/folder" | patt=o repl=xx perl -pe 's/$ENV{patt}/$ENV{repl}/g'
/path/txx/a/fxxlder

(我通过了patt环境repl,因为perl -p意味着将命令行参数作为要处理的文件名。)

当然,这里patt被视为正则表达式,包含所有含义:

$ echo "/path/to/a/folder" | patt='a.' repl=x perl -pe 's/$ENV{patt}/$ENV{repl}/g'
/pxh/to/xfolder

因此,您需要转义点 ( \.) 和其他特殊字符,或者使用\Q$ENV{patt}

$ echo "/path/to/a/folder.txt" | patt=. repl=, perl -pe 's/\Q$ENV{patt}/$ENV{repl}/g'
/path/to/a/folder,txt

在上述两种情况(命令行参数和环境变量)中,操作系统和实用程序之间的接口将字符串作为 NUL 终止字符串传递,如 C 标准库所使用的那样。此接口使得无法在参数中注入文字 NUL 字节,并且sed -e 's\a\x\g'sed 使用文字反斜杠作为s命令的分隔符。

答案4

@cerving' 的答案很接近,但不需要使用 tr。

cat pathsList| sed -z 's/\n/\x0/g'

-z用作\x0分隔符。它本质上将您的文件变成一个长字符串(如果 pathsList 尚未包含\x0)。因此,您的文件不应太大而无法容纳可用内存。

相关内容