Bir çıktı grep ile iki dosyaya nasıl bölünür?


14

mycommand.shİki kez çalıştıramayacağım bir senaryom var. Çıktı iki farklı dosyaya bir regex eşleşen satırları içeren bir dosya ve bir regex eşleşmeyen satırları içeren bir dosya bölmek istiyorum. Ne olmak istiyorum temelde böyle bir şeydir:

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

Ben sadece bir dosyaya çıkış ve sonra -v seçeneği ile ve olmadan iki farklı greps yönlendirebilirsiniz biliyorum ve çıktılarını iki farklı dosyalara yönlendirebilirsiniz. Ama bunu bir grep ile yapmanın mümkün olup olmadığını merak ediyordum.

Peki, istediğimi tek bir satırda elde etmek mümkün mü?

Yanıtlar:


20

Bunu başarmanın birçok yolu vardır.

Awk kullanma

Aşağıda coolregexdosya1 ile eşleşen tüm satırlar gönderilir . Diğer tüm satırlar dosya2'ye gider:

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

Nasıl çalışır:

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

    Normal ifadeyle eşleşen tüm satırlar coolregexyazdırılır file1. Ardından, kalan tüm komutları atlıyoruz ve nexthatta yeniden başlamak için atlıyoruz .

  2. 1

    Diğer tüm satırlar stdout'a gönderilir. 1awk'ın hat baskısı için şifreli kısayolu.

Birden fazla akışa bölmek de mümkündür:

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

Süreç ikamesini kullanma

Bu, awk çözümü kadar zarif değil, ancak tamlık için, işlem ikamesi ile birleştirilmiş birden çok greps de kullanabiliriz:

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

Ayrıca birden fazla akışa ayrılabiliriz:

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

Oh harika! Dosya2 yerine başka bir awk yapmadan birkaç dosyaya bölmek de mümkün mü? Yani normal ifadeler örneğin üst üste gelebilir.
yukashima huksay

1
@aran Evet, awk çok esnektir. Tam olarak nasıl yapılır, normal ifadelerin üst üste gelmesine bağlıdır.
John1024

Çakışan normal ifadeleri desteklemese bile bir çözüm görmek isterim. üst üste binerek, alt kümenin kesişiminin sinirsiz bir şekilde boş olmaması gibi bir şey demek istiyorum.
yukashima huksay

1
Her iki yöntem için birden fazla akışlı cevap örneklerine ekledim.
John1024

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

w filename - mevcut desen alanını dosya adına yazın.

Tüm eşleşen hatlar için gitmek istiyorsanız file_1ve tüm eşleşmeyen satırları için file_2yapabileceğiniz:

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

veya

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

açıklama

  1. /pattern/!{p;d};
    • /pattern/!- olumsuzlama - bir satır içermiyorsa pattern.
    • p - geçerli desen alanını yazdırır.
    • d- desen alanını sil. Bir sonraki döngüye başlayın.
    • yani, bir çizgi desen içermiyorsa, bu satırı standart çıktıya yazdırır ve sonraki satırı seçer. file_2Bizim durumumuzda standart çıktı yönlendirilir . Çizgi ile eşleşmediğinde , sedkomut dosyasının ( w file_1) bir sonraki bölümüne ulaşılamaz.
  2. w file_1- bir çizgi desen içeriyorsa, /pattern/!{p;d};parça atlanır (çünkü yalnızca desen eşleşmediğinde yürütülür) ve dolayısıyla bu çizgi file_1.

Lütfen son çözüme biraz daha açıklama ekleyebilir misiniz?
yukashima huksay

@aran Açıklama eklendi. Ayrıca komut düzeltildi - file_1ve file_2doğru sıraya değiştirildi.
MiniMax

0

Bu sedçözümü sevdim çünkü bashisms'e dayanmıyor ve çıktı dosyalarını aynı temelde işliyor. AFAIK, istediğinizi yapan bağımsız bir Unix aracı yoktur, bu yüzden onu kendiniz programlamanız gerekir. İsviçre çakısı yaklaşımını terk edersek, senaryo dillerinden herhangi birini (Perl, Python, NodeJS) kullanabilirdik.

NodeJS'de böyle yapılırdı

  #!/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)));

Örnek kullanım

# 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 ve farklı bir normal ifade sözdiziminin kullanılmasının sakıncası yoksa:

#!/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)

kullanım

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

Misal

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.