删除文本文件中标签之间的行

Removing lines between tags in a text file

我有很多包含注释的文本文件。原始文本用包含单词的行标记:

START OF TEXT OF PASSAGE 1

END OF TEXT OF PASSAGE 1

显然我可以在每个文档中搜索短语 START OF TEXT 并删除它之前的所有内容。然后搜索 END OF TEXT 并开始选择要删除的文本,直到我到达下一个 START OF TEXT.

到目前为止我已经想出了这个设计:

#!/bin/bash

a="START OF PROJECT"
b="END OF PROJECT"

while read line; do
    if line contains a; do
         while read line; do
            'if line does not contain b'
               'append the line to output.txt'; fi
         done
     done
     fi
done

也许有更简单的方法使用 sed、awk、grep 和管道?

'for every document' 'loop through it doing this' ('find the original text between START and END' | >> output.txt)

可惜我bash很差,对sed/awk一窍不通。

这样做的原因是我正在组装一个巨大的文本文档,它是数千个标记文档的串联——每个文档都包含一些带注释的段落。

在Python中:

import re
with open('in.txt') as f, open('out.txt', 'w') as output:
    output.write('\n'.join(re.findall(r'START OF TEXT(.*?)END OF TEXT', f.read())))

这会读取输入,搜索以必要标记开始和结束的所有匹配项,捕获组中感兴趣的文本,在换行符上加入所有这些组,并将其写入结果文件。

awk 很容易做到。您将创建一个包含以下内容的脚本(我称之为 yank.awk):

#!/usr/bin/awk

/START OF PROJECT/ { capture = 1; next }
/END OF PROJECT/ { capture = 0 }
capture == 1 { print }

然后 运行 就像这样:

yank.awk in.txt > output.txt

也可以用 sedgrep:

sed -ne '/START OF PROJECT/,/END OF PROJECT/p' in.txt | grep -vE '(START|END) OF PROJECT' > output.txt

(另一个Python解决方案)

您可以 itertools.groupby 根据布尔值将行组合在一起 - 只需使用全局标志来跟踪您是否在块中,然后使用 groupby 进行分组块内或块外的行。然后丢弃那些不是块的:

sample_lines = """
lskdjflsdkjf
sldkjfsdlkjf
START OF TEXT
Asdlkfjlsdkfj
Bsldkjf
Clsdkjf
END OF TEXT
sldkfjlsdkjf
sdlkjfdklsjf
sdlkfjdlskjf
START OF TEXT
Dsdlkfjlsdkfj
Esldkjf
Flsdkjf
END OF TEXT
sldkfjlsdkjf
sdlkjfdklsjf
sdlkfjdlskjf
""".splitlines()

from itertools import groupby

in_block = False
def is_in_block(line):
    global in_block
    if line.startswith("END OF TEXT"):
        in_block = False
    ret = in_block
    if line.startswith("START OF TEXT"):
        in_block = True
    return ret

for lines_are_text,lines in groupby(sample_lines, key=is_in_block):
    if lines_are_text:
        print(list(lines))

给出:

['Asdlkfjlsdkfj', 'Bsldkjf', 'Clsdkjf']
['Dsdlkfjlsdkfj', 'Esldkjf', 'Flsdkjf']

看到第一组有以 A、B 和 C 开头的行,第二组由以 D、E 和 F 开头的行组成。

你可以按如下方式使用sed:

sed -n '/^START OF TEXT/,/^END OF TEXT/{/^\(START\|END\) OF TEXT/!p}' infile

或者,使用扩展正则表达式 (-r):

sed -rn '/^START OF TEXT/,/^END OF TEXT/{/^(START|END) OF TEXT/!p}' infile

-n 阻止 sed 默认打印。其余工作如下:

/^START OF TEXT/,/^END OF TEXT/ {  # For lines between these two matches
    /^\(START\|END\) OF TEXT/!p    # If the line does NOT match, print it
}

这适用于 GNU sed,可能需要一些调整才能 运行 与其他 seds。

听起来您需要的具体解决方案是:

awk '/END OF TEXT OF PASSAGE/{f=0} f; /START OF TEXT OF PASSAGE/{f=1}' file

有关 select 来自文件的文本的其他方法,请参阅 。

使用 Perl 的触发器运算符在标记之间打印文本

给定一个像这样的语料库:

START OF TEXT OF PASSAGE 1
foo
END OF TEXT OF PASSAGE 1

START OF TEXT OF PASSAGE 2
bar
END OF TEXT OF PASSAGE 2

您可以使用 Perl 触发器运算符在一定范围内进行处理。例如,从 shell 提示:

$ perl -ne 'if (/^START OF TEXT/ ... /^END OF TEXT/) {
               next if /^(?:START|END)/;
               print;
            }' /tmp/corpus
foo
bar

基本上,这个简短的 Perl 脚本循环遍历您的输入。当它找到您的开始和结束标签时,它会丢弃标签本身并打印其间的所有其他内容。

使用说明

语料库中段落之间的换行符是为了便于阅读。如果你的真实语料库在段落之间没有换行并不重要,只要文本标记总是从你的原始 post 中所示的行的开头开始。如果该假设不成立,那么您将需要调整用于识别文章开头和结尾的正则表达式。

您可以将多个文件传递给 Perl 脚本。同样,只要您不超过 shell.

的长度限制,它就没有实际区别。

如果您希望最终输出到标准输出以外的地方,只需使用shell 重定向。例如:

perl -ne 'if (/^START OF TEXT/ ... /^END OF TEXT/) {
               next if /^(?:START|END)/;
               print;
          }' /tmp/file1 /tmp/file2 /tmp/file3 > /tmp/output