Làm cách nào để tách một đầu ra thành hai tệp bằng grep?


14

Tôi có một kịch bản mycommand.shmà tôi không thể chạy hai lần. Tôi muốn phân tách đầu ra thành hai tệp khác nhau, một tệp chứa các dòng khớp với regex và một tệp chứa các dòng không khớp với regex. Những gì tôi muốn có về cơ bản là một cái gì đó như thế này:

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

Tôi biết tôi chỉ có thể chuyển hướng đầu ra sang một tệp và sau đó đến hai greps khác nhau có và không có tùy chọn -v và chuyển hướng đầu ra của chúng sang hai tệp khác nhau. Nhưng tôi đã tự hỏi liệu có thể làm điều đó với một grep không.

Vì vậy, có thể đạt được những gì tôi muốn trong một dòng duy nhất?

Câu trả lời:


20

Có nhiều cách để thực hiện điều này.

Sử dụng awk

Sau đây gửi bất kỳ dòng phù hợp coolregexvới tập tin1. Tất cả các dòng khác đi đến tập tin2:

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

Làm thế nào nó hoạt động:

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

    Bất kỳ dòng nào khớp với biểu thức chính quy coolregexđược in ra file1. Sau đó, chúng tôi bỏ qua tất cả các lệnh còn lại và nhảy để bắt đầu lại trên nextdòng.

  2. 1

    Tất cả các dòng khác được gửi đến thiết bị xuất chuẩn. 1là tốc ký mật mã của awk cho dòng in.

Cũng có thể chia thành nhiều luồng:

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

Sử dụng thay thế quá trình

Điều này không thanh lịch như giải pháp awk, nhưng để hoàn thiện, chúng ta cũng có thể sử dụng nhiều greps kết hợp với thay thế quá trình:

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

Chúng tôi cũng có thể chia thành nhiều luồng:

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

Ồ tuyệt! Cũng có thể chia nó thành nhiều tệp mà không cần thực hiện một awk khác thay vì file2? Tôi có nghĩa là theo cách mà regexes có thể chồng chéo chẳng hạn.
yukashima huksay

1
@aran Vâng, awk rất linh hoạt. Chính xác làm thế nào một người sẽ làm nó phụ thuộc vào cách các biểu thức trùng lặp.
John1024

Tôi rất thích xem một giải pháp ngay cả khi nó không hỗ trợ các biểu thức chồng chéo. bằng cách chồng chéo tôi có nghĩa là có giao lộ của tập hợp con không trống rỗng.
yukashima huksay

1
@aran Tôi đã thêm vào các ví dụ trả lời với nhiều luồng cho cả hai phương thức.
John1024

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

w filename - ghi không gian mẫu hiện tại vào tên tệp.

Nếu bạn muốn tất cả các dòng phù hợp đi đến file_1và tất cả các dòng không khớp file_2, bạn có thể làm:

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

hoặc là

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

Giải trình

  1. /pattern/!{p;d};
    • /pattern/!- phủ định - nếu một dòng không chứa pattern.
    • p - in không gian mẫu hiện tại.
    • d- xóa không gian mẫu. Bắt đầu chu kỳ tiếp theo.
    • vì vậy, nếu một dòng không chứa mẫu, nó sẽ in dòng này thành đầu ra tiêu chuẩn và chọn dòng tiếp theo. Đầu ra tiêu chuẩn được chuyển hướng đến file_2trong trường hợp của chúng tôi. Phần tiếp theo của sedtập lệnh ( w file_1) không đạt được trong khi dòng không khớp với mẫu.
  2. w file_1- nếu một dòng chứa mẫu, /pattern/!{p;d};phần bị bỏ qua (vì nó chỉ được thực hiện khi mẫu không khớp) và do đó, dòng này đi đến file_1.

Bạn có thể vui lòng thêm một số giải thích cho giải pháp cuối cùng?
yukashima huksay

@aran Giải thích thêm. Ngoài ra lệnh được sửa - file_1file_2được hoán đổi theo đúng thứ tự.
MiniMax

0

Tôi thích sedgiải pháp này vì nó không dựa vào bashism và xử lý các tệp đầu ra trên cùng một bước. AFAIK, không có công cụ Unix độc lập nào thực hiện những gì bạn muốn nên bạn cần tự lập trình nó. Nếu chúng ta từ bỏ cách tiếp cận dao quân đội Thụy Sĩ, chúng ta có thể sử dụng bất kỳ ngôn ngữ kịch bản nào (Perl, Python, NodeJS).

Đây là cách nó sẽ được thực hiện trong 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)));

Ví dụ sử dụng

# 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

Nếu bạn không phiền việc sử dụng Python và một cú pháp biểu thức chính quy khác:

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

Sử dụng

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

Thí dụ

printf '%s\n' foo bar baz | python3 match-split.py '^b' b.txt not-b.txt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.