【问题标题】:Using sed, extract text between first occurrence of a word1 and last occurrence of a word2使用 sed,在第一次出现 word1 和最后一次出现 word2 之间提取文本
【发布时间】:2020-06-30 14:58:16
【问题描述】:

我需要使用 sed 提取第一次出现的单词“BEGIN”和最后出现的单词“END”之间的文本。

输入:

line1
BEGIN
line2 
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END
line9
line10

预期输出:

BEGIN
line2 
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END

我的方法:

它提取 BEGIN 和 END 之间的文本。这里有两个 BEGIN & END 语句,我的解决方案是在这些单词之间提取文本。 我的解决方案无法在 word1 的第一次出现(BEGIN)和 word2 的最后一次出现(END)之间提取文本。

dsonachalam$ sed -n -e '/^BEGIN$/,/^END$/p' logs.txt
BEGIN
line2 
line3
END
BEGIN
line6
line7
ENDED
END

【问题讨论】:

  • 你基本上已经缓冲了从最后一个 END 到下一个保持空间的行。 #1。找到开始。 #2。找到 END。 #3。在保持空间中缓冲每一行。 #4。如果您的行是 END,则打印保持空间,清除保持空间并转到第 3 步。
  • 使用edprintf "%s\n" "/BEGIN/,?END?p" | ed -s logs.txt

标签: sed


【解决方案1】:
start=$(grep -n "BEGIN" $FILE_NAME |cut -f1 -d:|head -n 1)
end=$(grep -n "END" $FILE_NAME |cut -f1 -d:|tail -n 1)

sed -n $start,"$end"p $FILE_NAME

【讨论】:

  • 你也可以使用sed -n '/BEGIN/='sed -n '/END/='代替grep + cut ...和sed -n '/BEGIN/ {=; q;}'来避免head
  • 复制/粘贴到shellcheck.net 并查看mywiki.wooledge.org/Quotes 了解一些问题。此外,这将读取输入文件 3 次并生成 7 个子shell,这是过多的。
【解决方案2】:

如果文件小到足以容纳内存:

$ perl -0777 -ne 'print /(^BEGIN\n.*^END\n)/ms' ip.txt
BEGIN
line2 
line3
END
line4
line5
BEGIN
line6
line7
ENDED
END
line8
END

【讨论】:

    【解决方案3】:

    使用 2-pass 方法避免必须将任何文本存储在内存中,因此它适用于任何大小的输入文件,并通过 1 次调用 1 个标准 UNIX 工具以避免产生多个子 shell,以下将使用任何 awk在每个 UNIX 机器上的任何外壳中:

    $ awk '
        NR==FNR{ if (!beg && /BEGIN/) beg=NR; if (/END/) end=NR; next}
        (beg <= FNR) && (FNR <= end)
    ' file file
    BEGIN
    line2
    line3
    END
    line4
    line5
    BEGIN
    line6
    line7
    ENDED
    END
    line8
    END
    

    【讨论】:

      【解决方案4】:

      单行 sed 命令就足够了(使用 GNU sed):

      sed -E '/^BEGIN$/,$!d; :a; /(^|\n).*END$/{p;d}; $d; N; ba'
      

      /^BEGIN$/,$!d; 删除第一个 BEGIN 上方的行。 :a; /(^|\n).*END$/{p;d}; $d; N; ba 将(“slurps”)行累积到模式空间中。每当读取 END 行时,就会打印出累积的行并删除模式空间,开始新的循环。请注意,这种“slurping”方法可能会很慢,如果输入太大,甚至可能导致sed 进程崩溃。

      输入文件内容:

      line1
      BEGIN
      line2 
      line3
      END
      line4
      line5
      BEGIN
      line6
      line7
      ENDED
      END
      line8
      END
      line9
      line10
      

      并使用 GNU sed 4.8

      sed -E '/^BEGIN$/,$!d; :a; /(^|\n).*END$/{p;d}; $d; N; ba' inputfile
      

      打印

      BEGIN
      line2 
      line3
      END
      line4
      line5
      BEGIN
      line6
      line7
      ENDED
      END
      line8
      END
      

      另一种方法是:

      lastend=$(sed -n '/^END$/=' inputfile | tail -1)
      [[ -n $lastend ]] && sed -n "/^BEGIN\$/,${lastend}p" inputfile
      

      这种两遍方法不会受到“啜饮”线条的影响。

      【讨论】:

        【解决方案5】:

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

        sed -n '/\<BEGIN\>/{x;:a;n;/\<END\>/{x;p;ba};H;$!ba;x;//P}' file
        

        使用-n 选项设置自动打印关闭,然后关注包含单词BEGIN 的行之后的行。

        交换到保持空间 (HS) 并启动一个循环以获取下一行,如果该行包含单词 END 交换到 HS,打印其内容并重复。

        如果当前行不包含单词END,则将当前行追加到HS,除非它是文件结尾重复。

        在文件末尾,打印 HS 的第一行,如果它以 END 开头,并且无论条件允许文件处理终止。

        因此,只有在看到单词 BEGIN 时才会处理行,并且每次出现单词 END 时都会打印这些行。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-20
          • 2014-07-31
          • 2019-08-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-08
          相关资源
          最近更新 更多