我正在使用sed
内联编辑一个开放的标准多节、空格分隔的文件的特定节中的特定条目,该文件对某些数值常量进行编码。
我有一个工作表达式来实现这一点,但我还希望它在到达另一个节标题时不找到与内部模式匹配的内容,因为根据标准,这些节在理论上可能是无序的,而我要查找的标签/模式可以与文件的其他节匹配。
文件规范的抽象版本以部分标题开始,部分标题作为标题关键字字符串的列表,即PLANES
,THE_TRAINS
,AN_AUTOMOBILE
,BUSES``SUBMARINES
,small
。要进行标识,头关键字字符串必须在行的开头,后面必须跟一个空白字符(空格或制表符)。在该行或下一行上可能会有其他空格分隔的节特定参数,尽管大多数节没有这些参数。空行被忽略,因此可以用来提高可读性,但不能假定。'之后的任何内容或者“*”被假定为注释。在一个部分中,N个公共属性关键字(例如medium
,big
,huge
,##
,##.###
,*
)的给定组合的一组常量由后面的数字常量(例如!
或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]
、HUGE
、SUBMARINES
和HUGE
),该如何解决?这将防止在
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_B1
或DERP
(但不是SUB_HEADING_II
)开头的块中以SUB_HEADING_IV
开头的行改为以SUB_HEADING_III
或sed
开头的块中的II
,则此操作在任何版本的IV
中都会执行(尽管它不会覆盖原始文件):
sed '/^SUB_HEADING_I[IV]$/,/^$/ s/^LBL_B1.*/DERP/'
对于子目
LBL_B1
或DERP
范围内的行(我使用了符号的一致紧凑性)到空行(或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
)或分离(关于regex - 如果遇到其他多个节标题中的任何一个,如何退出Sed中的节模式匹配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36583151/