这是我在打算替换/path/to/a
为/path/to/b
usingNUL
作为分隔符/定界符时尝试的方法:
$ 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
)。因此,您的文件不应太大而无法容纳可用内存。