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
怎么运行的:
/[coolregex]/{print>"file1";next}
任何与正则表达式匹配的行
coolregex
都会打印到file1
.然后,我们跳过所有剩余的命令并跳转到重新开始就行了next
。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
解释
/pattern/!{p;d};
/pattern/!
- 否定 - 如果一行不包含pattern
.p
- 打印当前模式空间。d
- 删除模式空间。开始下一个周期。- 因此,如果某行不包含模式,则会将此行打印到标准输出并选择下一行。在我们的例子中,标准输出被重定向到
file_2
。当该行与模式不匹配时,未到达脚本的下一部分sed
( )。w file_1
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