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';适用于bashkshzsh):

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 标准的扩展

他们实现的扩展是什么不同(GNU sed 实现的更多)。 即使是它们实现的那些扩展在语法上也有部分不同

如果您需要同时支持 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 版本要求驱动

请注意,我分别使用简写 macOSLinux 表示 sed 的 BSD 和 GNU 版本,因为它们是每个版本的库存版本平台。但是,可以在 macOS 上安装 GNU sed,例如,使用 Homebrew 和 brew install gnu-sed

注意使用-r-E 标志时除外扩展正则表达式),下面的说明相当于编写 POSIX 兼容 sed 脚本。

为了符合 POSIX,您必须将自己限制在 POSIX BREs (basic regular expressions),不幸的是,顾名思义,这是非常基本的。警告:不要假设 \|、@支持 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)。遗憾的是,这意味着您将无法使用几个有用的构造,特别是:

字边界断言,因为它们是特定于平台的(例如,Linux 上的\<,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 stringsed 's/'"$(printf '\t')"'/-/' <<<$'a\tb' # command subst. with printf

在与s 命令一起使用的替换字符串中,假设不支持任何控制字符转义序列,因此再次包含控制字符。作为文字,如上。

仅限 Linux: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 不适用于换行符,因为尾随换行符被命令替换删除( $(...))。

ia 函数的文本参数同上不要使用控制字符序列 - 见下文。

标签和分支bt 函数的标签以及标签名称​​参数必须后跟 文字换行符或拼接的$'\n'。或者,使用多个 -e 选项并在标签名称之后终止每个选项。 仅限 Linux:sed -n '/a/ bLBL; d; :LBL p' &lt;&lt;&lt;$'a\nb' # -&gt; 'a' macOS Linux: 任一(实际换行符):sed -n '/a/ bLBL d; :LBL p' <<<$'a\nb' OR(拼接$\n 实例):sed -n '/a/ bLBL'$'\n''d; :LBL'$'\n''p' &lt;&lt;&lt;$'a\nb' 或(多个-e 选项):sed -n -e '/a/ bLBL' -e 'd; :LBL' -e 'p' &lt;&lt;&lt;$'a\nb' 函数 ia 用于插入/附加文本函数名称后跟 \,后跟 文字 换行符或在指定文本参数之前拼接$'\n'。 仅限 Linux:sed '1 i new first line' &lt;&lt;&lt;$'a\nb' # -&gt; 'new first line&lt;nl&gt;a&lt;nl&gt;b' macOS Linux:sed -e '1 i\'$'\n''new first line' &lt;&lt;&lt;$'a\nb' 注意: 没有-e,文本参数在 macOS 的输出中莫名其妙地没有换行符终止(错误?)。 请勿在文本参数中使用控制字符转义,例如 \n\t,因为它们仅在 Linux 上受支持。 如果文本参数因此具有实际的内部换行符,\-转义它们。 如果您想在文本参数之后放置其他命令,您必须使用(未转义的)换行符(无论是文字还是拼接)终止它,或者继续使用单独的 -e 选项(这是适用的一般要求所有版本)。

函数lists内部(...中包含的多个函数调用),一定要同时终止last函数,收盘前,与;

仅限 Linux: sed -n '1 p;q' &lt;&lt;&lt;$'a\nb' # -&gt; 'a' macOS Linux: sed -n '1 p;q;' &lt;&lt;&lt;$'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&amp;/' &lt;&lt;&lt;'dog' # -&gt; '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 中如何替换换行符

使用 sed 替换大量文件中的 Windows 换行符 - 但它没有

Sed命令详解+如何替换换行符

使用 sed(或 awk 或 tr)将换行符 \n 替换为表达式

sed ack 搜索/用字符串替换换行符

用sed 怎么替换包含换行符\n的文本.比如这些