BSD macOS Sed 与 GNU Sed 对比 POSIX Sed 规范

**macOS 使用了 sed [1]BSD 版本,它在很多方面都与 Linux 发行版附带的 GNU sed 版本不同。 ** ****

它们的共同点POSIX 规定的功能 :参见 POSIX sed 规范。

最轻便的方法使用 POSIX 才有的功能,然而,限制功能

  • 值得注意的是,POSIX 仅 指定基本正则表达式的****支持**,这些正则表达式具有许多限制(例如,根本不支持|(交替),不直接支持+?)以及不同的转义要求。

    • 警告: GNU sed(无 -r),确实支持\|\+\?,这是不符合 POSIX 标准; 使用 --posix 禁用 (见下文)。
  • 仅使用 POSIX 功能

    • (两个版本): 使用 -n-e 选项(特别是,不要使用 -E-r 来启用对扩展正则表达式的支持 )

    • GNU sed:添加选项 --posix,以确保只有 POSIX 的功能(你不严格需要这一点,但没有它,你最终可能会不小心使用了非 POSIX 功能没有注意到; 警告--posix 本身符合 POSIX 标准)

    • 仅使用 POSIX 功能意味着更严格的格式化要求(放弃 GNU sed 中提供的许多便利):

      • 通常不支持控制字符序列,例如\n\t
      • 标签和分支命令(例如,b必须后跟实际的换行符或通过单独的 -e 选项继续。
      • 请参阅下文了解详情。

但是,这两个版本都实现了 POSIX 标准的扩展** :**

  • 什么他们执行的扩展名不同 (GNU sed 实现更多)。
  • 甚至那些他们实现的扩展部分语法也不同

如果你需要支持 BOTH 平台(讨论差异):

  • 不兼容的功能:

    • 使用不带参数的 **-i 选项 **** (没有备份的就地更新)是不兼容的:

      • BSD sed:必须使用 -i ''
      • GNU sed:必须使用 -i(相当于:-i'') - 使用 -i ''不起作用。
    • -i **** 在 GNU sedBSD sed最新版本 (例如,在 FreeBSD 10 上) 明智地打开每个输入文件行编号,但是从 10.12 开始不在 macOS 上。请注意,在没有 -i 的情况下,所有版本的数字都会在输入文件中累积**。 ** ****


    • 如果最后输入线并不*具有尾随换行符* (和打印):

      • BSD sed总是在输出上附加换行符,即使输入行没有以一个结尾。
      • GNU sed保留尾部换行符状态,即仅当输入行以一个结尾时才附加换行符。
  • 共同特征:

    • 如果你将 sed 脚本限制为 BSD sed 所支持的脚本,它们通常也可以在 GNU sed 中工作 - 除了使用 -E 使用特定于平台的扩展正则表达式功能之外。显然,你还将放弃特定于 GNU 版本的扩展。见下一节。

跨平台支持指南 (OS X / BSD,Linux),受 BSD 版本更严格的要求驱动

请注意,下面偶尔使用短序 macOSLinux 来分别引用 sed 的 BSD 和 GNU 版本,因为它们是每个平台上的库存版本。但是,可以在 macOS 上安装 GNU sed,例如,使用带有 brew install gnu-sedHomebrew

注意除非使用 -r-E 标志扩展正则表达式),否则下面的说明相当于编写符合 POSIX 标准的 sed 脚本。

  • 对于 POSIX 兼容性,你必须限制自己使用 POSIX BRE( 基本正则表达式) ,不幸的是,顾名思义,它非常基本。
    警告 :不要认为支持\|\+\?:虽然 GNU sed 支持它们(除非使用 --posix),BSD sed 不支持 - 这些功能符合 POSIX 标准。
    虽然 \+\? 可以符合 POSIX 标准的方式进行模拟
    \{1,\}\+
    \{0,1\}\?,但
    \|(交替) 不能

  • 对于更强大的正则表达式,使用 -E (而不是 -r)来支持 ERE( 扩展正则表达式) (GNU sed 不记录 -E,但它确实作为 -r 的别名工作; 更新版本的 BSD sed,例如 on FreeBSD 10,现在也支持 -r,但是 10.12 的 macOS 版本没有 )。
    警告 :尽管使用 -r / -E 意味着你的命令根据定义并不符合 POSIX,但你仍必须限制自己使用 POSIX ERE(扩展正则表达式) 。遗憾的是,这意味着你将无法使用几种有用的结构,特别是:

    • 字边界断言,因为它们是特定平台的 (例如,Linux 上的\<,OS X 上的 [[:<]])。
    • 正则表达式中的反向引用 (而不是在 s 函数调用的替换字符串中对捕获组匹配的反向引用),因为 BSD sed扩展正则表达式中不支持它们 (但是,奇怪的是,在基本的,它们是 POSIX 规定的)。
  • 控制字符转义序列,如\n\t

    • 在正则表达式 (用于行选择的模式和 s 函数的第一个参数)中,假设只有\n 被识别为转义序列(很少使用,因为模式空间通常是单行 (不终止\n),但不是在一个字符类中,所以,例如,[^\n] 不起作用;(如果你的输入不包含控制字符。除了\t,你可以用 [[:print:][:blank:]] 模拟 [^\n];否则,拼接控制字符作为文字 [2] ) - 通常,将控制字符包含为文字**,或者通过拼接的 ANSI C 引用的字符串 (例如,$'\t')支持它(bash,ksh,zsh),或者通过使用 printf 命令替换 (例如,$(printf '\t'))** 。

      • 仅限 Linux:
        sed 's/\t/-/' <<<$'a\tb' # -> 'a-b'
      • OSX Linux:
        sed 's/'$'\t''/-/' <<<$'a\tb' # ANSI C-quoted string
        sed 's/'"$(printf '\t')"'/-/' <<<$'a\tb' # command subst. with printf
    • 在与 s 命令一起使用的替换字符串中假设支持 NO 控制字符转义序列,因此,再次包括控制字符。作为文字,如上所述。

      • 仅限 Linux:
        sed 's/-/\t/' <<<$'a-b' # -> 'a<tab>b'
      • macOS Linux:
        sed 's/-/'$'\t''/' <<<'a-b'
        sed 's/-/'"$(printf '\t')"'/' <<<'a-b'
    • 同上的文本参数ia 功能不使用控制字符序列 -见下文。

  • 标签和分支 :标签以及 bt 函数的 label-name 参数必须后跟文字换行符或拼接的 $'\n' 。或者,使用多个 -e 选项并在标签名称后面的每个选项终止。

    • 仅限 Linux:
      sed -n '/a/ bLBL; d; :LBL p' <<<$'a\nb' # -> 'a'
    • macOS Linux:
      • (实际换行):
        sed -n '/a/ bLBL d; :LBL p' <<<$'a\nb'
      • 或(拼接 $\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'
  • 函数 ia 用于插入/附加文本按照\的函数名称, 在指定文本参数之前跟随文字换行符或拼接的 $'\n'** 。

    • 仅限 Linux:
      sed '1 i new first line' <<<$'a\nb' # -> 'new first line<nl>a<nl>b'
    • OSX Linux:
      sed -e '1 i\'$'\n''new first line' <<<$'a\nb'
    • 注意:
      • 如果没有 -e,text 参数莫名其妙地不会在 macOS 输出上换行(bug?)。
      • 不要在 text 参数中**使用控制字符转义符,**例如\n\t,因为它们仅在 Linux 上受支持。
      • 因此,如果 text 参数具有实际的内部换行符,则\ - 转义它们。
      • 如果要在 text 参数后面添加其他命令,则必须使用(非转义)换行符(无论是文字还是拼接)终止它,或者继续使用单独的 -e 选项(这是适用于所有版本的一般要求)。
  • 函数列表内{...} 中包含多个函数调用),请务必在关闭 } 之前使用 ; 终止最后一个函数

    • 仅限 Linux:
      • sed -n '1 {p;q}' <<<$'a\nb' # -> 'a'
    • macOS Linux:
      • sed -n '1 {p;q;}' <<<$'a\nb'

**** BSD sed 完全缺少 GNU sed 特有的功能

如果你需要支持这两个平台,你将错过的 GNU 功能:

[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:] 结合使用,以实现非常强大的 [^\n] 仿真:
'[[: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'']