如果遇到其他几个部分 Headers 中的任何一个,如何退出 Sed 中的部分模式匹配?

How to Exit Sectional Pattern Matching in Sed if Any One of Several Other Section Headers is Encountered?

我正在使用 sed 内联编辑 open-standard multi-section、space-separated 文件的特定部分中的特定条目,该文件对某些数字常量进行编码。

我有一个工作表达式可以执行此操作,但我还希望它在到达另一个部分标题而没有找到与内部模式匹配的情况下退出,因为根据标准理论上这些部分可能会超出顺序和我正在寻找的 label/pattern 可以匹配文件的其他部分。

文件规范的抽象版本以 headers 部分开始,作为 header 关键字字符串的列表,即 PLANESTHE_TRAINSAN_AUTOMOBILEBUSES``SUBMARINES。要识别 header 关键字字符串必须位于行的开头并且后面必须跟一个白色 space 字符(space 或制表符)。该行或下一行可能有额外的 space 分隔 section-specific 参数,尽管大多数部分没有它们。空行被忽略,因此可以用来提高可读性,但不能假设。 '!' 之后的任何内容或 '*' 被假定为注释。在一个部分中,N 个公共属性关键字(例如 smallmediumbighuge)的某些给定组合的一组常量由数字常量(例如####.###) 随后。属性关键字跨多个部分使用,但不能保证在特定部分中找到。

一个例子是:

*
* Header comments
*

PLANES
!
! COMMENTS
!
BIG MEDIUM ##.### ##.###
BIG SMALL ##.### ##.###
...
SMALL SMALL ##.### ##.###
THE_TRAINS
!
! COMMENTS
!
MEDIUM MEDIUM SMALL ##.### ##.### ! COMMENT STUFF
MEDIUM SMALL SMALL ##.### ##.### ! COMMENT STUFF
...
BIG BIG BIG ##.### ##.###



AN_AUTOMOBILE 0.1 shift red
!
! COMMENTS
SMALL SMALL SMALL SMALL ##.### ##.### ##.###
SMALL MEDIUM SMALL SMALL ##.### ##.### ##.### ! COMMENT STUFF
...
BIG BIG BIG SMALL ##.### ##.### ##.###

BUSES
SMALL ##.### ##.### ## !
MEDIUM ##.### ##.### ## !
...
LARGE ##.### ##.### ## !
SUBMARINES
SMALL ##.### ##.### ## !
MEDIUM ##.### ##.### ## !
...
HUGE ##.### ##.### ## !

*! 之后的任何内容都被视为文件标准中的注释。

部分是通过遇到关键字后跟 space 来定义的。之后可能会有部分特定的填充变量(参见编辑示例中的shift),但最终每个部分都有一个数字常量列表,前面有一组 N 标识符,这些标识符对所有部分都是通用的。

白色 space 部分之间或部分内的行之间是任意的,可以添加以提高可读性,但不能假设。

如果顺序与我当前的文件相同,则模式:

sed -i '/SUBMARINES/{:keep_reading;n; /^MEDIUM.*$/!bkeep_reading s/^MEDIUM.*$/DERP/ }' file.dat

...有效。

如果从上面的表达式中我的意图行动不清楚,我的目标是替换以某个给定关键字(即 SUBMARINES[ \t])为首的小节中的某些模式(即 ^MEDIUM.*$)。在示例中,我只是将整个匹配行替换为 DERP。在真正的实现中,我会做一个特定于实现的替换,但我已经知道如何去做,而且它的细节对于如何在 sed 中使用 built-in micro-language 来尝试达到的主题来说是多余的该行,如果遇到其他小节而没有在目标小节中找到匹配项,则退出。

但是,如果这些部分顺序不正确,它可能会再次中断(即,如果我尝试替换 BUSES 中的 HUGE,它将继续到下一个部分,SUBMARINES 并替换那个,因为它在给定部分中找不到)

如果我遇到任何其他部分 headings/subheadings(即 PLANESBUSESAN_AUTOMOBILETHE_TRAINS),我该如何摆脱困境在我遇到给定的 section-heading 关键字后跟 space/tab(即 SUBMARINES[ \t])?

这将阻止替换 SUBMARINES 中以 HUGE 开头的行,而我的意图是仅替换以 HUGE 开头的行(如果它在 BUSES 中找到) .


编辑 1:

我觉得是这样的:

sed -i '/BUSES/{:keep_reading;n; /^HUGE.*$/!bkeep_reading /PLANES/\|/THE_TRAINS/\|/AN_AUTOMOBILE/\|/SUBMARINES/q s/^HUGE.*$/DERP/g }' file.dat

... 可以工作,但该表达式给出了错误:

sed: -e expression #1, char 60: unknown command: `\'


编辑 2:

我有一个semi-working解决方案:

sed -i '/BUSES/{:keep_reading;n; /^PLANES[ \t]\|^THE_TRAINS[ \t]\|^AN_AUTOMOBILE[ \t]\|^BUSES[ \t]/q; /^HUGE.*$/!bkeep_reading;  s/^HUGE.*$/DERP/g; }' file.dat

但我现在意识到,在内联编辑时,我以前的两个解决方案实际上都会删除 HUGE 之后的任何行。我没有意识到这一点,因为我匹配的标签恰好是文件中的最后一行。

上述模式正确退出,但截断了文件的剩余部分。这似乎是一个简单的修复——如何让文件的其余部分保持原样?


此外,鉴于此附加语法,是否有更好的工具可从命令行使用(即 perl、python 等?)

地址范围:

sed -i '/^SUB_HEADING_II$/,/^[A-Z_]\+$/{ s/^LBL_B1.*$/DERP/g }' file

替换将应用于 SUB_HEADING_II 和包含大写字符和下划线混合的下一行之间的行。

在找到comment after the answer by Kenavoz

之前

如果您想要将以 LBL_B1 开头的行更改为以 SUB_HEADING_IISUB_HEADING_IV(但不是 SUB_HEADING_III)开头的块中的 DERP,则这在 sed 的任何版本中都有效(尽管它不会覆盖原始文件):

 sed '/^SUB_HEADING_I[IV]$/,/^$/ s/^LBL_B1.*/DERP/'

对于副标题 IIIV 范围内的行(我使用符号的巧合紧凑性)直到空白行(或 EOF),替换 LBL_B1 的任何实例在一行的开头(加上它后面的任何内容) DERP.

如果sub-headings更加多样化,那么:

sed -e '/^SUB_HEADING_IV$/,/^$/ s/^LBL_B1.*/DERP/' \
    -e '/^DIVERSITY_REIGNS$/,/^$/ s/^LBL_B1.*/DERP/'

如果激活扩展的正则表达式(-r in GNU sed-E in BSD 或 Mac OS X sed) ,那么你可以使用(BSD 表示法,但这里唯一的区别是 -E-r):

sed -E '/^(SUB_HEADING_IV|DIVERSITY_REIGNS)$/,/^$/ s/^LBL_B1.*/DERP/'

这假设 sub-heading 行没有评论。如果可以评论,您必须在识别起始行的正则表达式上更加努力:

sed -E '/^(SUB_HEADING_IV|DIVERSITY_REIGNS)( *!.*)?$/,/^$/ s/^LBL_B1.*/DERP/'

我不清楚 * 是否可以用来启动 'tail comment';如果是这样,请将 ! 替换为 [!*]

找到后comment after the answer by Kenavoz

The subheading is distinguished by some small set of keywords. The file format specifies that white space lines are simply ignored, so you can't count on them being there or not being there. To make matters a bit more confusing one of the subheading keywords does have stuff after it (sort of like general settings for that group of things). But the basic rule of thumb is the section starts as soon as a line beginning with a particular keyword followed by a space is encountered and ends when another keyword followed by a space is encountered or the EOF is encountered.

鉴于下一节开始的修订规范,您需要扩展正则表达式功能(或支持基本正则表达式中的 \| 交替),并且您需要替换 /^$/ 用于部分结尾的符号,例如:

 sed -E '/^(SUB_HEADING_II|SUB_HEADING_IV)$/,/^(SUB_HEADING_I|SUB_HEADING_II|SUB_HEADING_III|SUB_HEADING_IV)$/ {
         s/^LBL_B1.*/DERP/; }'

BSD要求分号sed; GNU sed 不介意它是否存在。如果有超过 4 个 sub-headings,我可能会 'generate' 使用 Bash 数组的结束标记:

SH=( "SUB_HEADING_I" "THE_AUTOMOBILE" "A_SUBMARINE" "SUB_HEADING_II"
     "TRANSVERSE_COGITATION" "DIAMETRICALLY_OPPOSED" "SUB_HEADING_III"
     "CODSWALLOP" "SUB_HEADING_IV"
   )
EH="$(IFS="|"; echo "/^(${SH[*]})$/")"
sed -E '/^(SUB_HEADING_II|SUB_HEADING_IV)( *[!*].*)?$/,'"$EH"' s/^LBL_B1.*/DERP/'

请注意,使用 ${SH[*]} 而不是 ${SH[@]} 对这项工作至关重要,分号也是如此。

这里有一个(可能是主要的)问题。一旦一个小节标题被用来标记上一个小节的结束,它就不能作为另一个小节的开始,所以如果你需要编辑两个连续编辑的小节,你又要更加努力了。根据您的可移植性要求,我可能会查看 awk 或 Perl 或 Python。使用这些语言比使用 sed 更容易管理此类工作。如果需要空行(或其他固定的 end-of-subsection 标记),则 sed 能够很好地处理该过程。

当然,如果您只需要脚本在您的一台机器上运行,或者在一组基本上具有相同设置(sed 的相同版本)的机器上运行,您可以使用platform-specific 适合自己的特质。如果您在多个环境中工作,在您使用 platform-specific 功能时了解它会有所帮助。它可能仍然是正确的做法 — 只要您意识到在迁移到其他环境时将面临的问题(或者至少,将面临一些问题)。它不会让您感到意外,您将在尝试在新环境的生产中使用代码之前进行测试。


主题再次更新后

...以及评论中的一些代码...

由于空格,您在识别 header 部分时遇到了问题,并且 EH (结尾 header 是我的助记符,虽然它不是特别好)不允许用于标题关键字后的可选 material。我认为这段代码工作正常。

script.sh

SH=( "PLANES" "THE_TRAINS" "AN_AUTOMOBILE" "BUSES" "SUBMARINES" )
EH="$(IFS="|"; echo "/^(${SH[*]})([ !*].*)?$/")"
sed -E '/^BUSES([ !*].*)?$/,'"$EH"' s/^HUGE.*/DERP/' data

SH=( "PLANES" "THE_TRAINS" "AN_AUTOMOBILE" "BUSES" "SUBMARINES" )
EH="$(IFS="|"; echo "/^(${SH[*]})([ !*].*)?$/")"
sed -E '/^SUBMARINES([ !*].*)?$/,'"$EH"' s/^HUGE.*/DERP/' data

SH 和 EH 行在两个命令序列中应该是相同的。比较有趣的部分是 sed 脚本。在每种情况下,开始模式都是一个关键字 ([ !*].*)?$ 不匹配任何内容或注释或空白和标记直到行尾。类似地,在 EH 的 sub-section 标题关键字列表之后使用相同的正则表达式片段,因此在 sed.

范围的第二部分中

示例运行:

$ bash -x script.sh
+ '[' -f /etc/bashrc ']'
+ . /etc/bashrc
++ '[' -z '' ']'
++ return
+ alias 'r=fc -e -'
+ SH=("PLANES" "THE_TRAINS" "AN_AUTOMOBILE" "BUSES" "SUBMARINES")
++ IFS='|'
++ echo '/^(PLANES|THE_TRAINS|AN_AUTOMOBILE|BUSES|SUBMARINES)([ !*].*)?$/'
+ EH='/^(PLANES|THE_TRAINS|AN_AUTOMOBILE|BUSES|SUBMARINES)([ !*].*)?$/'
+ sed -E '/^BUSES([ !*].*)?$/,/^(PLANES|THE_TRAINS|AN_AUTOMOBILE|BUSES|SUBMARINES)([ !*].*)?$/ s/^HUGE.*/DERP/' data
*
* Header comments
*

PLANES
!
! COMMENTS
!
BIG MEDIUM ##.### ##.###
BIG SMALL ##.### ##.###
...
SMALL SMALL ##.### ##.###
THE_TRAINS
!
! COMMENTS
!
MEDIUM MEDIUM SMALL ##.### ##.### ! COMMENT STUFF
MEDIUM SMALL SMALL ##.### ##.### ! COMMENT STUFF
...
BIG BIG BIG ##.### ##.###



AN_AUTOMOBILE 0.1 shift red
!
! COMMENTS
SMALL SMALL SMALL SMALL ##.### ##.### ##.###
SMALL MEDIUM SMALL SMALL ##.### ##.### ##.### ! COMMENT STUFF
...
BIG BIG BIG SMALL ##.### ##.### ##.###

BUSES
SMALL ##.### ##.### ## !
MEDIUM ##.### ##.### ## !
...
LARGE ##.### ##.### ## !
SUBMARINES
SMALL ##.### ##.### ## !
MEDIUM ##.### ##.### ## !
...
HUGE ##.### ##.### ## !
+ SH=("PLANES" "THE_TRAINS" "AN_AUTOMOBILE" "BUSES" "SUBMARINES")
++ IFS='|'
++ echo '/^(PLANES|THE_TRAINS|AN_AUTOMOBILE|BUSES|SUBMARINES)([ !*].*)?$/'
+ EH='/^(PLANES|THE_TRAINS|AN_AUTOMOBILE|BUSES|SUBMARINES)([ !*].*)?$/'
+ sed -E '/^SUBMARINES([ !*].*)?$/,/^(PLANES|THE_TRAINS|AN_AUTOMOBILE|BUSES|SUBMARINES)([ !*].*)?$/ s/^HUGE.*/DERP/' data
*
* Header comments
*

PLANES
!
! COMMENTS
!
BIG MEDIUM ##.### ##.###
BIG SMALL ##.### ##.###
...
SMALL SMALL ##.### ##.###
THE_TRAINS
!
! COMMENTS
!
MEDIUM MEDIUM SMALL ##.### ##.### ! COMMENT STUFF
MEDIUM SMALL SMALL ##.### ##.### ! COMMENT STUFF
...
BIG BIG BIG ##.### ##.###



AN_AUTOMOBILE 0.1 shift red
!
! COMMENTS
SMALL SMALL SMALL SMALL ##.### ##.### ##.###
SMALL MEDIUM SMALL SMALL ##.### ##.### ##.### ! COMMENT STUFF
...
BIG BIG BIG SMALL ##.### ##.### ##.###

BUSES
SMALL ##.### ##.### ## !
MEDIUM ##.### ##.### ## !
...
LARGE ##.### ##.### ## !
SUBMARINES
SMALL ##.### ##.### ## !
MEDIUM ##.### ##.### ## !
...
DERP
$

一些可移植性说明

这些最初是对 now-deleted 回答的评论。

\| 的交替之类的东西在 sed 的版本中并不通用。有关 sed 的标准(最小公分母)定义,请参阅 sed and its link to Basic Regular Expressions 的 POSIX 规范。请注意 -i(以及 -r-E\|)不是标准的。 BSD sed 不支持(记录为)\| 表示法作为意思交替。

你可以用-E激活扩展的正则表达式,然后普通的|表示交替,但你必须担心其他反斜杠序列(\(\{ 和关闭 \)\}) 失去了反斜杠(或者反斜杠现在表示字面字符而不是扩展含义)。

-i 选项的语义在 GNU 和 BSD 之间是不同的。两者之间唯一的可移植符号具有 -i.bak 形式(提供扩展名为 .bak 的备份——使用的名称是可选的,但它必须是 non-empty 字符串,例如 .bak ).在 GNU 中获取 in-situ 备份sed,您使用 -i,没有附加扩展名;在 BSD sed 中,您使用 -i ''(一个单独的参数,即空字符串)。 non-empty 后缀可以在 BSD sed 中附加(-i.bak)或分离(-i .bak); GNU sed 要求附加它。