regex - 如果遇到其他多个节标题中的任何一个,如何退出Sed中的节模式匹配?

标签 regex bash sed pattern-matching editing

我正在使用sed内联编辑一个开放的标准多节、空格分隔的文件的特定节中的特定条目,该文件对某些数值常量进行编码。
我有一个工作表达式来实现这一点,但我还希望它在到达另一个节标题时不找到与内部模式匹配的内容,因为根据标准,这些节在理论上可能是无序的,而我要查找的标签/模式可以与文件的其他节匹配。
文件规范的抽象版本以部分标题开始,部分标题作为标题关键字字符串的列表,即PLANESTHE_TRAINSAN_AUTOMOBILEBUSES``SUBMARINESsmall。要进行标识,头关键字字符串必须在行的开头,后面必须跟一个空白字符(空格或制表符)。在该行或下一行上可能会有其他空格分隔的节特定参数,尽管大多数节没有这些参数。空行被忽略,因此可以用来提高可读性,但不能假定。'之后的任何内容或者“*”被假定为注释。在一个部分中,N个公共属性关键字(例如mediumbighuge####.###*)的给定组合的一组常量由后面的数字常量(例如!shift)定义。属性关键字跨多个节使用,但不能保证在特定节中找到。
例如:

*
* 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 ##.### ##.### ## !

^MEDIUM.*$SUBMARINES[ \t]之后的任何内容都被视为文件标准中的注释。
节是通过遇到后跟空格的关键字来定义的。之后可能会有特定于节的stuff变量(请参见编辑的示例中的DERP),但最终每个节都有一个数字常量列表,前面是一些N标识符集,这些标识符对所有节都是通用的。
节之间或节内的行之间的空白是任意的,可以添加空白以提高可读性,但不能假定为空白。
如果顺序与当前文件相同,则模式:
sed -i '/SUBMARINES/{:keep_reading;n; /^MEDIUM.*$/!bkeep_reading s/^MEDIUM.*$/DERP/ }' file.dat

…有效。
如果从上面的表达式中不清楚我的预期操作,我的目标是用给定的关键字(即HUGE)替换子部分中的某些模式(即BUSES)。在这个例子中,我只是用SUBMARINES替换了整个匹配行。在实际实现中,我会做一个特定于实现的替换,但我已经知道如何做到这一点,并且它的细节对于如何使用SED中内置的微语言来尝试达到该行来说是多余的,如果没有在目标子段中找到匹配的其他子段,则退出。
但同样,如果部分出现故障,它可能会中断(即,如果我试图替换PLANES中的BUSES,它将继续到下一小节AN_AUTOMOBILE并替换该小节,因为在给定小节中找不到它)
在遇到给定的节标题关键字和空格/制表符(即THE_TRAINS)之后,如果遇到这些其他节标题/副标题(即SUBMARINES[ \t]HUGESUBMARINESHUGE),该如何解决?
这将防止在BUSES中替换以HUGE开头的行,如果在中找到以开头的行,则我的意图是仅替换以开头的行。
编辑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表达式1,字符60:未知命令:`'
编辑2:
我有一个半工作的解决方案:
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

但我现在意识到,在内联编辑时,我以前的两个解决方案实际上都会删除之后的任何行。我没有意识到这一点,因为我匹配的标签恰好是文件中的最后一行。
上面的模式正确地退出,但是截断了文件的其余部分。这似乎是一个简单的修复方法——如何保持文件的其余部分不变?
另外,考虑到这个额外的语法,有没有更好的工具可以从命令行使用(例如perl、python等?)

最佳答案

在通过comment找到answer之后的Kenavoz之前
如果要将以LBL_B1DERP(但不是SUB_HEADING_II)开头的块中以SUB_HEADING_IV开头的行改为以SUB_HEADING_IIIsed开头的块中的II,则此操作在任何版本的IV中都会执行(尽管它不会覆盖原始文件):

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

对于子目LBL_B1DERP范围内的行(我使用了符号的一致紧凑性)到空行(或EOF),用-r替换行开头的任何sed实例(加上其后的任何内容)。
如果副标题更加多样化,那么:
sed -e '/^SUB_HEADING_IV$/,/^$/ s/^LBL_B1.*/DERP/' \
    -e '/^DIVERSITY_REIGNS$/,/^$/ s/^LBL_B1.*/DERP/'

如果激活扩展正则表达式(GNU-E中的sed,BSD或Mac OS X-E中的-r),则可以使用(BSD表示法,但这里唯一的区别是*vs!):
sed -E '/^(SUB_HEADING_IV|DIVERSITY_REIGNS)$/,/^$/ s/^LBL_B1.*/DERP/'

这假定子标题行上没有注释。如果注释是可能的,您必须在regex上更加努力地识别起始行:
sed -E '/^(SUB_HEADING_IV|DIVERSITY_REIGNS)( *!.*)?$/,/^$/ s/^LBL_B1.*/DERP/'

我不清楚[!*]是否可以用于启动“tail comment”;如果可以,请将\|替换为/^$/
在通过comment找到answer之后的Kenavoz之后
副标题用一些小的关键字来区分。文件格式指定只忽略空白行,因此不能指望它们存在或不存在。为了让事情更混乱一些,其中一个副标题关键字后面确实有内容(类似于该组内容的一般设置)。但基本的经验法则是,当遇到以特定关键字开头、后跟空格的行时,节就开始;当遇到另一个关键字、后跟空格或遇到EOF时,节就结束。
考虑到下一节开始时的修订规范,您需要扩展regex功能(或在基本regex中支持sed替换),并且您需要将节结束时的sed标记替换为其他选项,例如:
 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${SH[*]}需要分号;GNU${SH[@]}不介意分号是否存在。如果有超过4个子标题,我可能会使用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/'

请注意,使用awk而不是sed对于此工作至关重要,分号也是如此。
这有一个(可能是主要的)问题。一旦使用子节标题标记上一节的结尾,就不能将其用作其他子节的开头,因此如果需要编辑编辑的两个连续子节,则必须再次更加努力。根据您的可移植性需求,我可能会查看sed或Perl或Python。在这些语言中管理这类工作比在sed中更容易。如果需要空白线(或分段标记的其他固定端),则EH能够很好地处理该过程。
当然,如果您只需要脚本在一台机器上工作,或者在一组基本上都有相同设置的机器上工作(相同版本的script.sh),那么您可以使用特定于平台的特性来适应自己。如果您在多个环境中工作,那么在使用特定于平台的功能时了解它会有帮助。这可能仍然是正确的做法-只要你知道在迁移到其他环境时会面临的问题(或者至少会有问题要面对)。它不会让您感到意外,您将在尝试在新环境的生产环境中使用代码之前进行测试。
在对主要问题进行另一次更新之后
…还有一些注释中的代码…
由于空格,您在识别节标题时遇到问题,并且sed(结束标题是我的助记符,虽然不是特别好)不允许在标题关键字之后使用可选材料。我认为这个代码是正确的。
([ !*].*)?$
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行在两个命令序列中应该是相同的。稍微有趣的部分是EH脚本。在每种情况下,start模式都是一个关键字,其中sed不匹配任何内容,或者是一个注释,或者是一个空白,并标记到行尾。类似地,在\|的赋值中的子节标题关键字列表之后以及因此在sed的范围的第二部分中使用相同的regex片段。
示例运行:
$ 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
$

一些可移植性说明
这些最初是对现在被删除的答案的评论。
像与sed交替这样的事情在sed的不同版本中并不普遍。有关-i的标准(最低公分母)定义,请参阅-r的POSIX规范及其到Basic Regular Expressions的链接。注意-E(和\|\|-E)不是标准的。在BSD sed中,|符号不支持(记录为)作为意义替换。
您可以使用\(激活扩展正则表达式,然后纯\{表示替换,但是您必须担心其他反斜杠序列(\)\}和关闭的-i-i.bak)会丢失它们的反斜杠(或者反斜杠现在表示文字字符,而不是扩展含义)。
.bak选项的语义在GNU和BSD之间是不同的。两者之间唯一的可移植符号的形式是.bak(提供扩展名为sed的备份-使用的名称是可选的,但必须是非空字符串,如-i)。要在GNUsed中获得原位备份,可以使用不附加扩展名的-i '';在BSD-i.bak中,可以使用-i .bak(一个单独的参数,即空字符串)。在BSDsed中,非空后缀可以附加(sed)或分离();GNU要求附加它。

关于regex - 如果遇到其他多个节标题中的任何一个,如何退出Sed中的节模式匹配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36583151/

相关文章:

html - 我想在 HTML 标记中的正则表达式中添加引号

excel - 将 Bash 中的 CSV 读入字典/关联数组

linux - 在 Makefile 中在 Linux 和 MacOS 上使用 sed

string - 想要更改前缀字符,然后在更改的字符串中添加后缀字符

regex - 用于匹配和目标的 URL 重定向插件正则表达式输入

objective-c - 动态生成枚举参数?

c# - 使用C#解析文本文件

bash - 在 OS X 上找到 brew 的安装位置

bash 完成不像以前那样工作——没有转义文件名中的空格和奇怪的东西等

bash - 如何用sed删除文本或空行?