如何使用 grep 将输出拆分为两个文件?

如何使用 grep 将输出拆分为两个文件?

mycommand.sh我有一个不能运行两次的脚本。我想将输出拆分为两个不同的文件,一个文件包含与正则表达式匹配的行,另一个文件包含与正则表达式不匹配的行。我想要的基本上是这样的:

./mycommand.sh | grep -E 'some|very*|cool[regex].here;)' --match file1.txt --not-match file2.txt

我知道我可以将输出重定向到一个文件,然后重定向到带有和不带有 -v 选项的两个不同的 grep,并将它们的输出重定向到两个不同的文件。但我只是想知道是否可以用一个 grep 来完成它。

那么,是否可以通过一行实现我想要的目标?

答案1

有很多方法可以实现这一点。

使用 awk

以下命令发送coolregex与 file1 匹配的所有行。所有其他行都转到 file2:

./mycommand.sh | awk '/[coolregex]/{print>"file1";next} 1' >file2

怎么运行的:

  1. /[coolregex]/{print>"file1";next}

    任何与正则表达式匹配的行coolregex都会打印到file1.然后,我们跳过所有剩余的命令并跳转到重新开始就行了next

  2. 1

    所有其他行都发送到标准输出。 1是 awk 的 print-the-line 的神秘简写。

也可以分成多个流:

./mycommand.sh | awk '/regex1/{print>"file1"} /regex2/{print>"file2"} /regex3/{print>"file3"}'

使用进程替换

这不像 awk 解决方案那么优雅,但为了完整性,我们还可以使用多个 grep 与进程替换相结合:

./mycommand.sh | tee >(grep 'coolregex' >File1) | grep -v 'coolregex' >File2

我们还可以分成多个流:

./mycommand.sh | tee >(grep 'coolregex' >File1) >(grep 'otherregex' >File3) >(grep 'anotherregex' >File4) | grep -v 'coolregex' >File2

答案2

sed -n -e '/pattern_1/w file_1' -e '/pattern_2/w file_2' input.txt

w filename- 将当前模式空间写入文件名。

如果您希望所有匹配的行都转到file_1,所有不匹配的行都转到file_2,您可以这样做:

sed -n -e '/pattern/w file_1' -e '/pattern/!w file_2' input.txt

或者

sed -n '/pattern/!{p;d}; w file_1' input.txt > file_2

解释

  1. /pattern/!{p;d};
    • /pattern/!- 否定 - 如果一行不包含pattern.
    • p- 打印当前模式空间。
    • d- 删除模式空间。开始下一个周期。
    • 因此,如果某行不包含模式,则会将此行打印到标准输出并选择下一行。在我们的例子中,标准输出被重定向到file_2。当该行与模式不匹配时,未到达脚本的下一部分sed( )。w file_1
  2. w file_1- 如果一行包含模式,则该/pattern/!{p;d};部分将被跳过(因为它仅在模式不匹配时执行),因此,该行将转到file_1.

答案3

我喜欢这个sed解决方案,因为它不依赖 bashisms 并且以相同的基础对待输出文件。 AFAIK,没有独立的 Unix 工具可以完成您想要的操作,因此您需要自己编程。如果我们放弃瑞士军刀方法,我们可以使用任何脚本语言(Perl、Python、NodeJS)。

这就是 NodeJS 中的做法

  #!/usr/bin/env node

  const fs = require('fs');
  const {stderr, stdout, argv} = process;

  const pattern = new RegExp(argv[2] || '');
  const yes = argv[3] ? fs.createWriteStream(argv[3]) : stdout;
  const no = argv[4] ? fs.createWriteStream(argv[4]) : stderr;

  const out = [no, yes];

  const partition = predicate => e => {
    const didMatch = Number(!!predicate(e));
    out[didMatch].write(e + '\n');
  };

  fs.readFileSync(process.stdin.fd)
    .toString()
    .split('\n')
    .forEach(partition(line => line.match(pattern)));

用法示例

# Using designated files
./mycommand.sh | partition.js pattern file1.txt file2.txt

# Using standard output streams
./partition.js pattern > file1.txt 2> file2.txt

答案4

如果您不介意使用 Python 和不同的正则表达式语法:

#!/usr/bin/env python3
import sys, re

regex, os1, os2 = sys.argv[1:]
regex = re.compile(regex)
with open(os1, 'w') as os1, open(os2, 'w') as os2:
    os = (os1, os2)
    for line in sys.stdin:
        end = len(line) - line.endswith('\n')
        os[regex.search(line, 0, end) is not None].write(line)

用法

./match-split.py PATTERN FILE-MATCH FILE-NOMATCH

例子

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt

相关内容