bash-delete 如果字符串存在于两个模式之间

bash-delete if string exists between two patterns

我一直在尝试使用 sed 来完成以下任务。假设我有以下文件(注意:我的实际文件比这个更复杂):

hello world
foo bar
people people
target
something
done

我想检查 target 是否存在于两个模式之间,在这个例子中,在行 foo bardone 之间(包括两行),如果target 确实存在。

我知道如何使用这个 sed 命令删除两个模式之间的线条:

sed '/people.*/,/done/d' file

但我只想在两个字符串匹配之间存在字符串 target 时删除它。

我的逻辑是这样的:

sed -n '/people.*/,/done/p' file | check if target string exists | delete entire pattern found by sed

编辑

我忘了说在同一行的 target 之前和 target 之后可以有任意数量的单词。

Sed

如果在其中找到 $pattern,这将从 $start 删除到 $end

sed ":a;N;$!ba; s/$start.*$pattern.*$end//g"

这里有两个步骤(语句):

  1. 将整个文件作为单个字符串读取(可能不好,具体取决于文件大小)。一个很好的解释,参考。唯一的区别是 $!ba 之前的附加反引号,以使其与双引号一起使用,这对于在 sed 行内传递 Bash 变量很有用。
  2. 常规老search/replace.

Perl

要处理非贪婪匹配,如果允许使用 Perl,请使用:

perl -0777 -p -e 's/$start.*?$pattern.*?$end//s'

这也会将整个文件作为字符串读取。末尾的 /s 告诉它在正则表达式匹配中包含换行符。使用 .* 而不是 .*?回到贪婪搜索。

如果文件多次包含 done,则无需先将整个文件读入内存并引起贪婪匹配问题即可执行此操作的方法是

sed '/^people/ { :loop; N; /\ndone/ ! b loop; /target/ d }' filename

在 Mac OS X 上显然需要在右括号之前有一个换行符,因此您可以将代码放入多行字符串文字中:

sed '/^people/ { :loop; N; /\ndone/ ! b loop; /target/ d 
}' filename

或者把这个(在任何情况下更易读)版本的代码放在一个文件中,比如 foo.sed,然后使用 sed -f foo.sed filename:

/^people/ {
  :loop
  N
  /\ndone/ ! b loop
  /target/ d
}

代码的工作原理如下:

/^people/ {

在以 "people"

开头的行中
  :loop
  N
  /\ndone/ ! b loop

在循环中获取更多行,直到一行以 done 开头(这将是 \ndone 第一次出现在模式 space 中)

  /target/ d

如果其中某处有 target,请丢弃整个东西

}

否则照常进行(这意味着打印模式 space 因为我们没有将 -n 传递给 sed)。

稳健性的一个可能改进是

sed '/^people/ { :loop; N; /\ndone$/! { $! b loop }; /target/ d }' filename

/^people/ {
  :loop
  N
  /\ndone/ ! {
    $ ! b loop
  }
  /target/ d
}

随着变化 /\ndone$/! { $! b loop }。这将在文件的最后一行结束循环,即使没有遇到 done,其效果是文件末尾未完成的 people 部分不会被丢弃(除非它们包含 target).

sed 是用于在单行上进行简单替换的出色工具,但在 20 世纪 70 年代中期发明 awk 时,它用于处理多行的所有构造都已过时,因此只需使用 awk 来实现简单、清晰、健壮等。例如使用 GNU awk 进行多字符 RS:

$ awk -v RS='^$' '{sub(/\nfoo bar\n.*target.*\ndone\n/,""); print}' file
hello world

晚回答

sed '/^foo bar *$/,/^done *$/{/^done *$/!{H;d};/^done *$/{H;g;s/.*//g;x;/.*target.*/d;s/^.//g}}'

找到/^foo bar *$/,/^done *$/

之间的所有行
/foo bar/,/done/

这个 /^done *$/!{H;d} 从 foo bar 中取出所有行但不是最后一行 "done" 并将其放在保留 space 中。之后从模式 space.

中删除这些行

/^done *$/{H;g;s/.*//g;x; 取最后一行 "done" 并将其附加到保留 space。现在我们有了从 foo bar 行到 done in hold space 行的所有行。之后我们清除模式 space 中的所有内容,并将保持 space 中的行范围与模式 space 中的空行交换(这是为了始终保持当定位 "foo bar" 和 "done" 之间的另一个行范围时,保持 space 为空。

终于

/.*target.*/d 

我们测试 "target" 是否在多重模式 space 中。如果是,"foo bar" 和 "done" 之间的行范围将被删除

这样可以避免将整个文件作为单个字符串读取

例子

hello world
foo bar
people people
target
something
done
foo bar
.....
.....
.....
done
foo bar
people people
test
something
done

结果

hello world
foo bar
.....
.....
.....
done
foo bar
people people
test
something
done

注意:从 "foo bar" 到 "done" 的行范围以及包含 "target" 的行正在被删除