使用Bash从文本文件中提取两个标记之间的行

21

我有一个文本文件,其内容如下:

random useless text 
<!-- this is token 1 --> 
para1 
para2 
para3 
<!-- this is token 2 --> 
random useless text again

我想提取标记之间的文本(不包括标记本身)。我尝试使用##和%%来提取标记之间的数据,但它没有起作用。我认为这并不适用于操作如此大的文本文件。有什么建议吗?也许可以使用awk或sed?

7个回答

41

不需要使用headtailgrep,也不需要多次读取文件:

sed -n '/<!-- this is token 1 -->/{:a;n;/<!-- this is token 2 -->/b;p;ba}' inputfile

解释:

  • -n - 不进行隐式打印
  • /<!-- this is token 1 -->/{ - 如果找到起始标记,则
    • :a - 标签“a”
      • n - 读取下一行
      • /<!-- this is token 2 -->/q - 如果是结束标记,则退出
      • p - 否则,打印该行
    • ba - 跳转到标签“a”
  • } 结束判断

1
我注意到另一件事情:使用FreeBSD的sed,sed -n '/^----$/{n;/^----$/q;p;}' /dev/null可以正常工作(没有输出),但是添加循环(即sed -n '/^----$/{:a;n;/^----$/q;p;ba}' /dev/null)会导致sed出现unexpected EOF (pending }'s)错误。我必须将使用循环的版本拆分成多行来编写。 :-( - Frerich Raabe
@DennisWilliamson:嘿,拆分脚本是一个聪明的解决方法。我会尝试一下!顺便给你点赞,我认为“sed”被严重低估了! - Frerich Raabe
@DennisWilliamson 如何使用Bash变量代替<!-- this is token 2 --> - Ilia
@IliaRostovtsev:将Bash变量合并到sed命令中可能会很棘手,因为您需要使用双引号才能对变量进行评估。您可能需要通过转义某些字符来避免意外的评估。您还应该在变量名称中使用花括号,以将其与其他可能被视为名称一部分的字符分开。在这种特殊情况下:var='<!-- this is token 2 -->'; sed -n "/<!-- this is token 1 -->/{:a;n;/${var}/b;p;ba}" inputfile应该可以工作(未经测试)。 - Dennis Williamson
@DennisWilliamson 谢谢您。我知道这个问题。问题是我的字符串包含 /**/,它会出现错误 sed: -e expression #1, char 3: unknown command: *'。但如果我用 \ 转义它们,那么就会出现一堆其他错误,比如 sed: -e expression #1, char 20: unknown command: ush: p: command not foundsh: ba}: command not found。看起来它是按字面意思执行的。有什么想法吗? - Ilia
显示剩余4条评论

26

您可以使用sed提取它,包括令牌。 然后使用head和tail剥离令牌。

... | sed -n "/这是令牌1/,/这是令牌2/p" | head -n-1 | tail -n+2


在 MacOS 上使用负数行数作为 head 命令的参数会导致 head: illegal line count -- -1 的错误提示。 - balupton

1

请尝试以下方法:

sed -n '/<!-- this is token 1 -->/,/<!-- this is token 2 -->/p' your_input_file
        | egrep -v '<!-- this is token . -->'

1
也许sed和awk有更优雅的解决方案,但我有一个“穷人”的方法,使用grep、cut、head和tail。
#!/bin/bash

dataFile="/path/to/some/data.txt"
startToken="token 1"
stopToken="token 2"

startTokenLine=$( grep -n "${startToken}" "${dataFile}" | cut -f 1 -d':' )
stopTokenLine=$( grep -n "${stopToken}" "${dataFile}" | cut -f 1 -d':' )

let stopTokenLine=stopTokenLine-1
let tailLines=stopTokenLine-startTokenLine

head -n ${stopTokenLine} ${dataFile} | tail -n ${tailLines}

1

不需要调用强大的sed / awk / perl。您可以仅使用"bash"来完成:

#!/bin/bash
STARTFLAG="false"
while read LINE; do
    if [ "$STARTFLAG" == "true" ]; then
            if [ "$LINE" == '<!-- this is token 2 -->' ];then
                    exit
            else
                    echo "$LINE"
            fi
    elif [ "$LINE" == '<!-- this is token 1 -->' ]; then
            STARTFLAG="true"
            continue
    fi
done < t.txt

亲切的问候
realex

0

对于这样的事情,我会选择Perl,因为它具有(除其他外)sedawk的功能。类似以下代码(注意-未经测试):

my $recording = 0;
my @results = ();
while (<STDIN>) {
   chomp;
   if (/token 1/) {
      $recording = 1;
   }
   else if (/token 2/) {
      $recording = 0;
   }
   else if ($recording) {
      push @results, $_;
   }
}

0
sed -n "/TOKEN1/,/TOKEN2/p" <YOUR INPUT FILE> | sed -e '/TOKEN1/d' -e '/TOKEN2/d'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接