sed 没有给我正确的 Mac 换行符替换操作 - GNU sed 和 BSD / OSX sed [重复]
Posted
技术标签:
【中文标题】sed 没有给我正确的 Mac 换行符替换操作 - GNU sed 和 BSD / OSX sed [重复]【英文标题】:sed not giving me correct substitute operation for newline with Mac - differences between GNU sed and BSD / OSX sed [duplicate] 【发布时间】:2014-08-08 03:07:45 【问题描述】:我正在使用这个参考:sed help: matching and replacing a literal "\n" (not the newline)
我有一个文件“test1.txt”,其中包含一个字符串 hello\ngoodbye
我使用这个命令搜索并用实际的换行符替换“\n”:
sed -i '' 's/\\n/\n/g' test1.txt
但结果是:hellongoodbye。 它只是将“\n”替换为“n”,而不是实际的新行。这与 /t 相同,它将留下一个“t”而不是一个制表符。
'' 表示 MAC 中未定义的错误:http://mpdaugherty.wordpress.com/2010/05/27/difference-with-sed-in-place-editing-on-mac-os-x-vs-linux/
更新:
@hek2mgl 建议的两个命令我都试过了:
sed -i 's/\\n/\n/g' test.txt
# Or:
sed -i'' 's/\\n/\n/g' test.txt
虽然它们可能适用于 Linux,但对于 MAC OS,我收到以下错误:
sed: 1: "test1.txt": undefined label 'est1.txt'
不知道为什么我不能让它工作。提前致谢。
【问题讨论】:
我在 Linux(目前在 Ubuntu)中尝试了同样的事情,它适用于sed -i 's/\\n/\n/g' input.txt
。
错误信息意味着它将字符串 test1.txt
解释为 sed 脚本,其中 t
是命令,一个名为 ext1.txt
的标签的条件分支,当然不是存在。奇怪的是空参数是必要的,但我认为相对于换行符的问题,这是一个红鲱鱼。
【参考方案1】:
对于 BSD/macOS sed
,要在s
函数调用的替换字符串 中使用换行符,您必须使用\
-转义实际 换行 - 转义序列\n
不支持(与调用的regex 部分不同)。
任一:只需插入一个实际换行符:
sed -i '' 's/\\n/\
/g' test1.txt
或:使用ANSI C-quoted string ($'...'
) 拼接换行符($'\n'
;适用于bash
、ksh
或zsh
):
sed -i '' 's/\\n/\'$'\n''/g' test1.txt
GNU sed
,相比之下,确实在替换字符串中识别 \n
;请继续阅读以全面了解这两种实现之间的差异。
GNU sed
(Linux) 和 BSD/macOS sed
之间的区别
macOS 使用 sed
[1] 的 BSD 版本,这在许多方面与 GNU em> Linux 发行版附带的sed
版本。
它们的共同点是POSIX规定的功能:参见the POSIX sed
spec.
最便携的方法是仅使用 POSIX 功能,但是,限制了功能:
值得注意的是,POSIX 指定仅支持 basic 正则表达式,它有许多限制(例如,根本不支持|
(替代),不直接支持对于+
和?
) 以及不同的转义要求。
警告:GNU sed
(没有-r
),确实支持\|
、\+
和\?
,这不符合POSIX;使用--posix
禁用(见下文)。
仅使用 POSIX 功能:
(两个版本):仅使用-n
和-e
选项(值得注意的是,不要使用-E
或-r
来开启支持对于扩展正则表达式)
GNU sed
:添加选项 --posix
以确保仅 POSIX 功能(您并不严格需要它,但如果没有它,您最终可能会在不注意的情况下无意中使用非 POSIX 功能;警告 em>:--posix
本身 不 POSIX兼容)
使用仅 POSIX 功能意味着更严格的格式要求(放弃 GNU sed
中提供的许多便利):
一般不支持控制字符序列,例如 \n
和 \t
。
标签和分支命令(例如,b
)必须后跟 实际换行符或通过单独的 -e
选项继续。
详见下文。
但是,两个版本都实现了对 POSIX 标准的扩展:
他们实现的扩展是什么不同(GNUsed
实现的更多)。
即使是它们都实现的那些扩展在语法上也有部分不同。
如果您需要同时支持 BOTH 平台(讨论差异):
不兼容功能: 使用-i
选项没有一个参数(就地更新没有备份)是不兼容的:
BSD sed
: 必须使用 -i ''
GNU sed
:必须只使用 -i
(等效:-i''
) - 使用 -i ''
不起作用。
-i
明智地在 GNU sed
和 最近 版本中打开 per-input-file 行编号 BSD sed
(例如,在 FreeBSD 10 上),但 在 10.15 之后的 macOS 上没有。
请注意,在没有-i
所有 版本的情况下,输入文件中累积的行数。
如果 last 输入行 not 有尾随换行符(并且被打印):
BSD sed
:总是在输出中附加一个换行符,即使输入行不是以一个结尾。
GNU sed
:保留尾随换行状态,即,仅当输入行以 1 结尾时,它才会附加换行符。
常见特点:
如果您将 sed
脚本限制在 BSD sed
支持的范围内,它们通常也可以在 GNU sed
中工作 - 除了使用特定于平台的 扩展 正则表达式功能与-E
。显然,您还将放弃特定于 GNU 版本的扩展。请参阅下一节。
跨平台支持指南(macOS/BSD、Linux),由更严格的 BSD 版本要求驱动:
请注意,我分别使用简写 macOS 和 Linux 表示 sed
的 BSD 和 GNU 版本,因为它们是每个版本的库存版本平台。但是,可以在 macOS 上安装 GNU sed
,例如,使用 Homebrew 和 brew install gnu-sed
。
注意:使用-r
和-E
标志时除外(扩展正则表达式),下面的说明相当于编写 POSIX 兼容 sed
脚本。
\|
、@支持 987654394@ 和 \?
:虽然 GNU sed
支持它们(除非使用 --posix
),但 BSD sed
不支持 - 这些功能不 POSIX 兼容。
虽然 \+
和 \?
可以以符合 POSIX 的方式模拟:\1,\
for \+
,\0,1\
for @ 987654404@,\|
(替代)不能,很遗憾。
对于更强大的正则表达式,使用-E
(而不是-r
)来支持ERE(扩展正则表达式)( GNU sed
没有记录 -E
,但它作为 -r
的别名在那里工作;更新 版本的 BSD sed
,例如在 FreeBSD 10 上,现在也支持 @ 987654412@,但 10.10 的 macOS 版本不是)。警告:即使使用 -r
/ -E
意味着您的命令是由定义不 POSIX 兼容,您仍必须限制自己使用POSIX EREs (extended regular expressions)。遗憾的是,这意味着您将无法使用几个有用的构造,特别是:
\<
,OS X 上的[[:<]]
)。
反向引用在正则表达式中(与 s
函数调用的替换字符串中捕获组匹配的“反向引用”相反),因为 BSD sed
没有t 在 extended 正则表达式中支持它们(但奇怪的是,在 basic 正则表达式中支持它们,它们是 POSIX 强制的)。
控制字符转义序列,例如\n
和\t
:
s
函数的第一个参数中),假设只有\n
被识别为转义序列(很少使用,因为模式空间通常是 单行 行(不终止 \n
),但不在 字符类 内,因此,例如,[^\n]
不起作用;(如果您的输入不包含控制字符。除了\t
,您可以模拟[^\n]
和[[:print:][:blank:]]
;否则,将控制字符拼接成文字[2]) - 通常,将控制字符包含为 文字,或者通过在支持它的 shell (@ 987654429@ksh、zsh
),或通过 使用printf
的命令替换(例如,"$(printf '\t')"
)。
仅限 Linux:sed 's/\t/-/' <<<$'a\tb' # -> 'a-b'
macOS 和 Linux:sed 's/'$'\t''/-/' <<<$'a\tb' # ANSI C-quoted string
sed 's/'"$(printf '\t')"'/-/' <<<$'a\tb' # command subst. with printf
在与s
命令一起使用的替换字符串中,假设不支持任何控制字符转义序列,因此再次包含控制字符。作为文字,如上。
sed 's/-/\t/' <<<$'a-b' # -> 'a<tab>b'
sed 's/-/\n/' <<<$'a-b' # -> 'a<newline>b'
macOS 和 Linux:sed 's/-/'$'\t''/' <<<'a-b'
sed 's/-/'"$(printf '\t')"'/' <<<'a-b'
sed 's/-/\'$'\n''/' <<<'a-b'
请注意,换行符需要反斜杠转义,以便它们被正确解释为替换字符串的一部分而不是命令的结尾,并且使用 printf
不适用于换行符,因为尾随换行符被命令替换删除( $(...)
)。
i
和 a
函数的文本参数同上:不要使用控制字符序列 - 见下文。
b
和 t
函数的标签以及标签名称参数必须后跟 文字换行符或拼接的$'\n'
。或者,使用多个 -e
选项并在标签名称之后终止每个选项。
仅限 Linux:sed -n '/a/ bLBL; d; :LBL p' <<<$'a\nb' # -> 'a'
macOS 和 Linux:
任一(实际换行符):sed -n '/a/ bLBL
d; :LBL
p' <<<$'a\nb'
OR(拼接$\n
实例):sed -n '/a/ bLBL'$'\n''d; :LBL'$'\n''p' <<<$'a\nb'
或(多个-e
选项):sed -n -e '/a/ bLBL' -e 'd; :LBL' -e 'p' <<<$'a\nb'
函数 i
和 a
用于插入/附加文本:函数名称后跟 \
,后跟 文字 换行符或在指定文本参数之前拼接$'\n'
。
仅限 Linux:sed '1 i new first line' <<<$'a\nb' # -> 'new first line<nl>a<nl>b'
macOS 和 Linux:sed -e '1 i\'$'\n''new first line' <<<$'a\nb'
注意:
没有-e
,文本参数在 macOS 的输出中莫名其妙地没有换行符终止(错误?)。
请勿在文本参数中使用控制字符转义,例如 \n
和 \t
,因为它们仅在 Linux 上受支持。
如果文本参数因此具有实际的内部换行符,\
-转义它们。
如果您想在文本参数之后放置其他命令,您必须使用(未转义的)换行符(无论是文字还是拼接)终止它,或者继续使用单独的 -e
选项(这是适用的一般要求所有版本)。
函数lists内部(...
中包含的多个函数调用),一定要同时终止last函数,收盘前,与
;
。
sed -n '1 p;q' <<<$'a\nb' # -> 'a'
macOS 和 Linux:
sed -n '1 p;q;' <<<$'a\nb'
使用 -f
选项(从文件中读取命令),只有 GNU sed
支持 -
作为标准输入的占位符; 使用-f /dev/stdin
可移植地从标准输入读取命令,包括从here-documents 读取命令(假设您的平台支持/dev/stdin
,现在通常是这种情况)。
GNU sed
特有的功能完全从 BSD sed
中消失:
如果您需要同时支持这两个平台,您会错过的 GNU 功能:
各种正则表达式匹配和替换选项(包括用于行选择的模式和s
函数的第一个参数):
I
选项用于不区分大小写的正则表达式匹配(难以置信的是,BSD sed
根本不支持此功能)。
用于多行匹配的M
选项(其中^
/ $
匹配每一行的开始/结束)
有关特定于s
函数的其他选项,请参阅https://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command
转义序列
与替换相关的转义序列,例如s///
函数的替换参数中的\u
,允许子字符串操作,在限制范围内;例如,sed 's/^./\u&/' <<<'dog' # -> 'Dog'
- 见http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command
控制字符转义序列:除了\n
、\t
、...、基于代码点的转义;例如,以下所有转义(十六进制、八进制、十进制)都表示单引号 ('
):\x27
、\o047
、\d039
- 请参阅 https://www.gnu.org/software/sed/manual/sed.html#Escapes
地址扩展,如first~step
匹配每一步,addr, +N
匹配addr
之后的N行,... - 见http://www.gnu.org/software/sed/manual/sed.html#Addresses
[1] macOS sed
版本比其他类似 BSD 的系统(例如 FreeBSD 和 PC-BSD)上的版本旧。不幸的是,这意味着您不能假设在 FreeBSD 中运行的功能,例如,在 macOS 上也可以[相同] 运行。
[2] ANSI C 引用的字符串 $'\001\002\003\004\005\006\007\010\011\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\177'
包含除 \n
(和 NUL)之外的所有 ASCII 控制字符,因此您可以将它与 [:print:]
结合使用,以获得非常强大的 @ 模拟987654502@:'[[:print:]'$'\001\002\003\004\005\006\007\010\011\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\177'']
【讨论】:
+1 很好的答案。我只是在黑暗中摸索。 好吧,你不是一个人在摸索……很好的答案,谢谢。【参考方案2】:这可能看起来有点奇怪,但请尝试:
sed -i '' 's/\\n/\
/g' test1.txt
即,使用 实际 换行符而不是 \n
。
解释是你有一个奇怪的sed
!
详见mac sed手册:https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/sed.1.html
在s
命令的描述中,它说:
A line can be split by substituting a newline character into it. To specify
a newline character in the replacement string, precede it with a backslash.
另外,在-i
选项的描述中,它说扩展不是可选的,如果你不想要一个,你必须指定一个空参数。所以最后一切都是有意义的!
【讨论】:
我收到此错误:“sed: 1: "s/\\n/ /g": 未转义的换行符在替代模式中" 我已经编辑过了。再试一次(在换行符前加上反斜杠)。 这成功了! :D,你能在你的回答中解释一下吗?以及为什么它不能按预期使用 MAC 而不是 linux? @BrandonLing 查看编辑。以上是关于sed 没有给我正确的 Mac 换行符替换操作 - GNU sed 和 BSD / OSX sed [重复]的主要内容,如果未能解决你的问题,请参考以下文章
使用 sed 替换大量文件中的 Windows 换行符 - 但它没有