为什么我不能用 ed(1) 打印这个正则表达式范围?

mbi*_*ras 6 regular-expression ed

我完全困惑为什么当有两个范围\xe2\x80\x94see file2.tf file\xe2\x80\x94;时我不能用 ed 打印这个范围 但当只有一个范围\xe2\x80\x94see file1.tf file\xe2\x80\x94 时我可以打印,并且我可以使用 gsed (macOS 上的 GNU sed)命令进行打印;但我无法用 ed 打印。请考虑我的 shell 会话并澄清我的误解:

\n
$ ed -s file1.tf <<<',n'\n1   # some comment\n2   module "hello_world" {\n3     source = "./mydir"\n4   }\n5   # another comment\n$ ed -s file1.tf <<<'/module.*world/,/}/p'\nmodule "hello_world" {\n  source = "./mydir"\n}\n$ ed -s file2.tf <<<',n'\n1   # some comment\n2   module "hello_world" {\n3     source = "./mydir"\n4   }\n5   # another comment\n6   # some comment\n7   module "hello_again" {\n8     source = "./anotherdir"\n9   }\n10  # another comment\n$ ed -s file2.tf <<<'/module.*again/,/}/p'\n?\n$ gsed -n '/module.*again/,/}/p' file2.tf\nmodule "hello_again" {\n  source = "./anotherdir"\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更新:相反方向有效;但我不知道为什么:

\n
$ ed -s file2.tf <<<'?module.*again?,?}?p'\nmodule "hello_again" {\n  source = "./anotherdir"\n}\n
Run Code Online (Sandbox Code Playgroud)\n

更新 2:该??方法实际上并没有按预期工作(如果有像file3.tf文件示例中那样的三个部分),请参阅答案以获取解释。

\n
$ ed -s file3.tf <<<,n\n1   # some comment\n2   module "hello_world" {\n3     source = "./mydir"\n4   }\n5   # another comment\n6   # some comment\n7   module "hello_again" {\n8     source = "./anotherdir"\n9   }\n10  # another comment\n11  # some comment\n12  module "hello_yet_again" {\n13    source = "./yetanotherdir"\n14  }\n15  # another comment\n$ ed -s file3.tf <<<'?module.*hello_again?,?}?p'\nmodule "hello_again" {\n  source = "./anotherdir"\n}\n# another comment\n# some comment\nmodule "hello_yet_again" {\n  source = "./yetanotherdir"\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Kus*_*nda 10

导致问题的ed和之间的区别在于正则表达式地址范围的处理方式。sed

在 中sed,首先找到起始行,然后将命令应用于所有行,直到找到结束行。

另一方面,在 中ed,范围的起始线和终止线是相对于当前行计算的。然后该命令将应用于该行范围内的所有行。

在使用/module.*again/, /}/ined和第二个文件的情况下,当前行位于文件的末尾(因为您刚刚将其加载到缓冲区中)。该范围的起始行计算为第 7 行,结束行为第 4 行(当前行之后与第二个表达式匹配的第一行)。您不能有一个向后运行的范围,因此您会收到错误。

使用 时?module.*again?, ?}?,搜索会反向进行,因此范围的起始位置与之前一样计算为第 7 行(只有一行与表达式 匹配module.*again),但现在范围的末尾是第 9 行,该行大于 7,所以你没有同样的问题。

您的解决方案不是通过?使用代替作为正则表达式分隔符来反转搜索/,因为这将无法正确找到三个部分的中间部分(您的示例中只有两个)。相反,首先将光标移动到该范围的第一行,然后将命令从该行应用到该部分的末尾:

/module.*again/; /}/ p
Run Code Online (Sandbox Code Playgroud)

这看起来非常类似于/module.*again/, /}/ p,但是通过使用;代替,,在找到起始地址之前不会计算结束地址。这是sed默认处理范围的方式,因为它没有其他选择(不将完整文档存储在内存中)。

该表达式本质上与较长的表达式相同,后者显式地将光标移动到范围的开头,然后将命令从现在的当前行应用到该部分的末尾:

/module.*again/; ., /}/ p
Run Code Online (Sandbox Code Playgroud)

POSIX 规范ed,对于vs.是这么说的;

地址之间应使用<comma>( ,) 或<semicolon>字符 ( ;) 分隔。如果有<semicolon>分隔符,则将当前行(.)设置为第一个地址,然后才计算第二个地址。此功能可用于确定向前和向后搜索的起始行。