如何让 sed 取消注释由换行符分隔的多个段落之一?

如何让 sed 取消注释由换行符分隔的多个段落之一?

说明:使用 GNU sed。

现状 - ~/.screenrc,最后几行:

# name synthpop
# screen -t script emacs -nw /home/$USER/bin/synthpop

# name thrashmetal
# screen -t script emacs -nw /home/$USER/bin/thrashmetal
# number 1
# split -v
# focus
# chdir "/home/$USER/thrashmetal/src"
# screen -t scr watch nl asdkjlek.html
# number 2
# focus
# split
# focus
# screen -t output watch thrashmetal asdkjlek.html
# number 3
# focus
# split
# focus
# chdir "/home/$USER/thrashmetal/plan"
# screen -t soll watch less soll
# number 4
# focus

# name darkwave
# screen -t script emacs -nw /home/$USER/bin/darkwave

目的:

# name synthpop
# screen -t script emacs -nw /home/$USER/bin/synthpop

name thrashmetal
screen -t script emacs -nw /home/$USER/bin/thrashmetal
number 1
split -v
focus
chdir "/home/$USER/thrashmetal/src"
screen -t scr watch nl asdkjlek.html
number 2
focus
split
focus
screen -t output watch thrashmetal asdkjlek.html
number 3
focus
split
focus
chdir "/home/$USER/thrashmetal/plan"
screen -t soll watch less soll
number 4
focus

# name darkwave
# screen -t script emacs -nw /home/$USER/bin/darkwave

我当前的尝试几乎可以完成工作,但一旦出现该行就无法停止,这只是一个新行,没有任何注释符号或字母数字字符:

sed 's+^# \(name thrashmetal\)+\1+;:a;s+# ++;ta' .screenrc

解释、意图:

  • '一系列要做的事情的开始

  • s代替

  • +使用“+”作为分隔符

  • # …逐行浏览,直到找到以“#”开头的行...

  • \(启动组以供以后反向参考

  • name thrashmetal并继续“名称thrashmetal”

  • \)组结束

  • +\1+仅用组部分替换该行中的第一个匹配项,不带“#”

  • ;然后,从那时起,做另一件事……

  • :a做一个名为“a”的循环

  • ;s对于接下来的每一行,您逐行替换......

  • +设置“+”作为分隔符

  • #该行中“#”的第一个匹配项,无论该行中其后的内容如何...

  • ++什么都没有,有效地删除它

  • ;ta只要你失败了就去做应该是当你到达段落末尾时,然后是分隔 thrashmetal 块和 darkwave 块的 2 个换行符,然后返回到“a”所在的位置

  • '一系列要做的事情的结束

  • .screenrc执行有关我当前目录中的文件“.screenrc”的一系列操作 - 我的主目录

标准输出:

# name synthpop
# screen -t script emacs -nw /home/$USER/bin/synthpop

name thrashmetal
screen -t script emacs -nw /home/$USER/bin/thrashmetal
number 1
split -v
focus
chdir "/home/$USER/thrashmetal/src"
screen -t scr watch nl asdkjlek.html
number 2
focus
split
focus
screen -t output watch thrashmetal asdkjlek.html
number 3
focus
split
focus
chdir "/home/$USER/thrashmetal/plan"
screen -t soll watch less soll
number 4
focus

name darkwave
screen -t script emacs -nw /home/$USER/bin/darkwave

如何解决这个问题?我做错了什么,循环不会在到达 thrashmetal 段落末尾时立即停止,而是简单地跳到下一段(darkwave 段落)并继续?

我猜想,那个循环中的任何东西都一定是错误的……也许是“t”,但“T”或“b”也不会剪掉它……

答案1

sed '/^# name thrashmetal$/,/^$/ s/^# //' file

这将应用替换,#从范围内的每一行中删除初始和后续的空格字符。

该范围以行匹配开始^# name thrashmetal$,以第一个后续行匹配^$(空行)结束。

或者,直接翻译成awk

awk '/^# name thrashmetal$/,/^$/ { sub(/^# /, "") }; 1' file

此处,尾随1{ print $0 }or的{ print }缩写,导致输出当前(可能已修改)行(文本的打印在sed脚本中是隐式的)。


对于任何对ed编辑器感兴趣的人,您可以使用以下编辑命令来解决此问题:

/^# name thrashmetal$/;.,/^$/ s/^# //

这会将光标移动到所需段落的第一行,并将替换应用于该行以及后面的所有行,直到到达空行。

我们不能使用

/^# name thrashmetal$/,/^$/ s/^# //

...因为地址是相对于当前行计算的,并且当我们启动编辑器时当前行位于文件的末尾。由于我们位于文件的末尾,因此命令中的范围将无效,因为地址/^$/将是比起始地址更早的行(相对于当前行,第一个空行位于段落开头之前) 。


您自己的方法已更正(将分隔符从加号更改为默认值并分成多个表达式,这更便携):

sed -e '/^# \(name thrashmetal\)/!b' -e 's//\1/' -e :a -e n -e 's/^# //' -e ta file

我没有进行初始替换,而是让段落初始行的匹配来确定是否跳过编辑脚本的其余部分。b如果未遇到该行,则跳过该行。

如果找到该行,我们将对其进行修改并创建标签a。然后,我们打印当前缓冲区并使用下一行替换其内容n。循环像以前一样继续,#如果缓冲区的内容发生变化,则删除子字符串并循环回标签。

您最初尝试的主要问题是,

sed 's+^# \(name thrashmetal\)+\1+;:a;s+# ++;ta' .screenrc

……就是那个“a -loop”是无条件的,因此适用于输入的每一行,无论初始替换是否成功或失败(您显示前两行不受命令影响,但我认为这是一个复制粘贴错误)。循环也不会读取下一行,因此它只会运行一次每行(除非#在一行上找到多个子字符串)。

我的修复方法是将初始替换分为测试和替换,然后确保“ a-loop”读取下一行输入,直到替换失败。我也不会替换#任何一行上的多个或嵌入(内部)字符串,只会替换行开头的第一个字符串。

答案2

这个脚本似乎可以做你想做的事:

H
/^$/ T show
$ T show
d

:show
x
s/\n//
/^# name thrashmetal/ {
  s/# //
  s/\n# /\n/g
}

我们有效地将事物分割成空行分隔的块。当我们找到以 开头的块时# name thrashmetal,我们删除#行开头的所有字符串。

给定您的示例输入,这将产生输出:

$ sed -f filter.sed < example.conf
# name synthpop
# screen -t script emacs -nw /home/$USER/bin/synthpop

name thrashmetal
screen -t script emacs -nw /home/$USER/bin/thrashmetal
number 1
split -v
focus
chdir "/home/$USER/thrashmetal/src"
screen -t scr watch nl asdkjlek.html
number 2
focus
split
focus
screen -t output watch thrashmetal asdkjlek.html
number 3
focus
split
focus
chdir "/home/$USER/thrashmetal/plan"
screen -t soll watch less soll
number 4
focus

# name darkwave
# screen -t script emacs -nw /home/$USER/bin/darkwave

带注释的 sed 脚本:

# Append current line to the hold space
H

# If we find a blank line or reach EOF, print out the
# current chunk
/^$/ T show
$ T show

# Delete the current line (we'll print it later in the
# show routine)
d

:show
# Swap the pattern space and the hold space
x

# Remove the initial "\n" that was added when we appended
# the first line with the H command
s/\n//

# If this is our target chunk, remove the comments
/^# name thrashmetal/ {
  s/# //
  s/\n# /\n/g
}

答案3

您还可以考虑使用 awk:

$ awk -v RS= -v ORS='\n\n' '/thrashmetal/{sub(/^# /,"");gsub(/\n# /,"\n",$0)}1'  .screenrc
# name synthpop
# screen -t script emacs -nw /home/$USER/bin/synthpop 

name thrashmetal
screen -t script emacs -nw /home/$USER/bin/thrashmetal
number 1
split -v
focus
chdir "/home/$USER/thrashmetal/src"
screen -t scr watch nl asdkjlek.html
number 2
focus
split
focus
screen -t output watch thrashmetal asdkjlek.html
number 3
focus
split
focus
chdir "/home/$USER/thrashmetal/plan"
screen -t soll watch less soll
number 4
focus 

# name darkwave
# screen -t script emacs -nw /home/$USER/bin/darkwave 

在这里,通过将 RS(输入记录分隔符)设置为空白,您可以将段落视为单行。

答案4

使用(以前称为 Perl_6)

~$ raku -ne 'if / thrashmetal /fff^/ \# \s name / { .subst(/^ "\# " /).put } else { $_.put };'  file

或者:

~$ raku -ne 'put (/ thrashmetal /fff^/ \# \s name /) ?? $_.subst(/^ "\# " /)  !! $_ ;'  file

Raku 是 Perl 家族的一种编程语言。您可以使用 Raku-ne类似 awk 的非自动打印命令行标志和 if/else 语句来更改所选行。

这里的关键是使用Raku的/…ON…/fffˆ/…OFF…/“触发器”运算符,当第一个识别域匹配时翻转ON,然后当第二个识别域匹配时翻转OFF。基于fff,这里我们添加一个尾随插入^符号,指示 Raku 删除第二个匹配行。

因此,您可以一次匹配所需的注释掉记录/段落,用于thrashmetal第一个匹配和\# \s name第二个匹配。使用 取消注释已识别的记录.subst(/^ "\# " /)

输入示例:

# name synthpop
# screen -t script emacs -nw /home/$USER/bin/synthpop

# name thrashmetal
# screen -t script emacs -nw /home/$USER/bin/thrashmetal
# number 1
# split -v
# focus
# chdir "/home/$USER/thrashmetal/src"
# screen -t scr watch nl asdkjlek.html
# number 2
# focus
# split
# focus
# screen -t output watch thrashmetal asdkjlek.html
# number 3
# focus
# split
# focus
# chdir "/home/$USER/thrashmetal/plan"
# screen -t soll watch less soll
# number 4
# focus

# name darkwave
# screen -t script emacs -nw /home/$USER/bin/darkwave

示例输出:

# name synthpop
# screen -t script emacs -nw /home/$USER/bin/synthpop

name thrashmetal
screen -t script emacs -nw /home/$USER/bin/thrashmetal
number 1
split -v
focus
chdir "/home/$USER/thrashmetal/src"
screen -t scr watch nl asdkjlek.html
number 2
focus
split
focus
screen -t output watch thrashmetal asdkjlek.html
number 3
focus
split
focus
chdir "/home/$USER/thrashmetal/plan"
screen -t soll watch less soll
number 4
focus

# name darkwave
# screen -t script emacs -nw /home/$USER/bin/darkwave

https://docs.raku.org/routine/fff%5E
https://raku.org

相关内容