使用sed / awk从小节中删除字符串

时间:2018-10-27 20:27:46

标签: regex perl awk sed

我有一个看起来像这样的文件:

bar
barfo
barfoo
barfooo
barfoooo

sample
sampleText1
sampleText2
sampleText3

prefix
prefixFooBar
prefixBarFoo

我希望sed(或awk)要做的是从其所有内容中删除引入一个节的字符串,以便最终得到:

bar
fo
foo
fooo
foooo

sample
Text1
Text2
Text3

prefix
FooBar
BarFoo

我尝试使用

sed -e -i '/([[:alpha:]]+)/,/^$/ s/\1//g' file

但是由于“无效的反向引用”而失败。

8 个答案:

答案 0 :(得分:5)

$ awk '{$0=substr($0,idx)} !idx{idx=length($0)+1} !NF{idx=0} 1' file
bar
fo
foo
fooo
foooo

sample
Text1
Text2
Text3

prefix
FooBar
BarFoo

答案 1 :(得分:3)

另一个awk

$ awk '{sub(pre,"")}1; !NF{pre=""} !pre{pre=$1}' file

bar
fo
foo
fooo
foooo

sample
Text1
Text2
Text3

prefix
FooBar
BarFoo

答案 2 :(得分:2)

perl -ple'
   if (!length($_)) { $re = "" }
   elsif (!length($re)) { $re = $_ }
   else { s/^\Q$re// }
'

注意:

  • 使用s/\Q$re//g删除行中的任何地方,而不仅仅是删除前缀。
  • 即使标题行包含特殊字符,例如\.*,这也可以使用。
  • 即使一行中有多个空白行,此方法也有效。
  • 有关完整用法,请参见Specifying file to process to Perl one-liner
  • 代码中的换行符是可选的(即可以删除)。

答案 3 :(得分:1)

sed解决方案,主要用来说明sed可能不是这样做的最佳选择:

$sed -E '1{h;b};/^$/{n;h;b};G;s/^(.*)(.*)\n\1$/\2/' infile
bar
fo
foo
fooo
foooo

sample
Text1
Text2
Text3

prefix
FooBar
BarFoo

这是它的工作方式:

1 {                   # on the first line
  h                   # copy pattern buffer to hold buffer
  b                   # skip to end of cycle
}
/^$/ {                # if line is empty
  n                   # get next line into pattern buffer
  h                   # copy pattern buffer to hold buffer
  b                   # skip to end of cycle
}
G                     # append hold buffer to pattern buffer
s/^(.*)(.*)\n\1$/\2/  # substitute

复杂的部分在替换中。替换之前,模式缓冲区保存如下内容:

prefixFooBar\nprefix

现在,替换匹配两个捕获组,第一个捕获组由\n和字符串结尾之间的内容引用-我们从保持缓冲区中获取的前缀。

然后替换是原始行的其余部分,并且删除了前缀。

备注:

  • 这适用于GNU sed;较旧的GNU sed版本可能需要-r而不是-E
  • -E只是为了方便;没有它,替换看起来像

    s/^\(.*\)\(.*\)\n\1$/\2/
    

    但仍然可以使用。

  • 对于macOS sed,它可与命令之间的文字换行符一起使用:

    sed -E '1{
    h
    b
    }
    /^$/{
    n
    h
    b
    }
    G
    s/^(.*)(.*)\n\2$/\2/' infile
    

答案 4 :(得分:1)

这是另一个sed解决方案。仅当段落开头的所有字符串都以主题行开头时,它才有效。

sed -e '1{h;b};/^$/{n;h;b};H;g;s/\(.*\)\n\1//;p;g;s/\n.*//;h;d' file
  • 1第一行:h复制以保留空间,b打印并继续下一行
  • /^$/空行:n打印并读取下一行,h复制以保留空间,b打印并继续
  • 所有(其他)行:
    • H追加以换行符容纳空间
    • g将保留空间复制到模式空间
    • s/\(.*\)\n\1//从模式空间中删除第一行及其内容,在第二行中
    • p打印图案空间
    • g将保留空间复制到模式空间,以便从H中删除新内容
    • /\n.*//删除新内容
    • h复制回保留空间
    • d删除图案空间

sed对于这些事情没有用。

由于s的搜索模式中没有分组,因此您获得“无效的反向引用”。

答案 5 :(得分:1)

另一个awk:

$ awk '{if(p&&match($0,"^" p))$0=substr($0,RLENGTH+1);else p=$0}1' file

输出:

bar
fo
foo
fooo
foooo

sample
Text1
Text2
Text3

prefix
FooBar
BarFoo

答案 6 :(得分:1)

这是另一个awk解决方案:

awk '{gsub(s,"")}1; s==""||!NF{s=$0}' file

优点:

  • 匹配被替换,无论它们在哪里
  • 所有匹配项被替换
  • 标题行的评估结果可能是0 / false
  • 标题行可能包含空格

缺点:

  • 标题行不得包含正则表达式元字符

答案 7 :(得分:1)

这可能对您有用(GNU sed):

sed 'G;s/^\(.\+\)\(.*\)\n\1$/\2/;t;s/\n.*//;h' file

将上一个键(如果是第一行,则不添加)添加到当前行。删除键和上一个键(如果它们匹配),打印当前行并重复。否则,密钥不匹配,请删除旧的附加密钥,将新密钥存储在保留空间中并打印新密钥。