grepで出力を2つのファイルに分割する方法は?


14

mycommand.sh2回実行できないスクリプトがあります。出力を2つの異なるファイルに分割します。1つのファイルには正規表現に一致する行が含まれ、もう1つのファイルには正規表現に一致しない行が含まれます。私が欲しいのは基本的に次のようなものです:

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

出力をファイルにリダイレクトし、次に-vオプションの有無にかかわらず2つの異なるgrepsにリダイレクトし、それらの出力を2つの異なるファイルにリダイレクトできることを知っています。しかし、1つのgrepで実行できるかどうか疑問に思っていました。

それで、私が望むものを1行で達成することは可能ですか?

回答:


20

これを実現するには多くの方法があります。

awkを使用する

以下は、一致coolregexする行をfile1に送信します。他のすべての行はfile2に移動します。

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

使い方:

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

    正規表現に一致する行はすべてに出力coolregexされfile1ます。次に、残りのすべてのコマンドをスキップし、ジャンプしてそのnext行からやり直します。

  2. 1

    他のすべての行はstdoutに送信されます。 1awkの行を印刷するための不可解な速記です。

複数のストリームに分割することもできます。

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

プロセス置換の使用

これはawkソリューションほどエレガントではありませんが、完全を期すために、複数のgrepsをプロセス置換と組み合わせて使用​​することもできます。

./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

かっこいい、イケてる!また、file2の代わりに別のawkを実行せずに複数のファイルに分割することは可能ですか?たとえば、正規表現が重複する可能性があるという意味です。
ユカシマフクサイ17

1
@aranはい、awkは非常に柔軟です。正確にどのように行うかは、正規表現がどのように重複するかによって異なります。
John1024

重複する正規表現をサポートしていない場合でも、解決策を見つけたいです。オーバーラップとは、サブセットの交差点が神経質に空にならないようなものです。
ユカシマフクサイ17

1
@aran両方の方法に複数のストリームを使用して回答例に追加しました。
John1024

8
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ます。

最後の解決策にさらに説明を加えていただけますか?
ユカシマフクサイ17

@aran説明が追加されました。また、コマンドが修正されました- file_1file_2正しい順序に交換されました。
MiniMax

0

sedbashismsに依存せず、同じフッターで出力ファイルを処理するため、ソリューションが気に入りました。私の知る限り、あなたが望むことをするスタンドアロンの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

0

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.