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

时间:2016-02-17 00:01:44

标签: bash awk sed text-processing

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

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和pipe?

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

不幸的是我在bash上很穷而且对sed / awk一无所知。

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

6 个答案:

答案 0 :(得分:2)

在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())))

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

答案 1 :(得分:0)

使用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

答案 2 :(得分:0)

(另一个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开头的行组成。

答案 3 :(得分:0)

您可以按如下方式使用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一起运行。

答案 4 :(得分:0)

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

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

有关从文件中选择文字的其他方法,请参阅https://stackoverflow.com/a/18409469/1745001

答案 5 :(得分:0)

使用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脚本会循环输入。当它找到你的开始和结束标签时,它会抛弃标签本身并打印其中的所有其他内容。

使用说明

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

您可以将多个文件传递给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