วิธีการแยกเอาต์พุตเป็นสองไฟล์ด้วย grep


14

ฉันมีสคริปต์mycommand.shที่ไม่สามารถเรียกใช้สองครั้ง ฉันต้องการแบ่งเอาต์พุตเป็นสองไฟล์ต่างกันหนึ่งไฟล์ที่มีบรรทัดที่ตรงกับ regex และหนึ่งไฟล์ที่มีบรรทัดที่ไม่ตรงกับ regex สิ่งที่ฉันหวังว่าจะมีคืออะไรแบบนี้:

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

ฉันรู้ว่าฉันสามารถเปลี่ยนเส้นทางการส่งออกไปยังไฟล์และจากนั้นไปยัง greps ที่ต่างกันสองตัวโดยมีและไม่มีตัวเลือก -v และเปลี่ยนเส้นทางเอาต์พุตไปยังสองไฟล์ที่แตกต่างกัน แต่ฉันกำลังสงสัยว่ามันเป็นไปได้ที่จะทำด้วย grep หนึ่งตัวหรือไม่

ดังนั้นเป็นไปได้หรือไม่ที่จะบรรลุสิ่งที่ฉันต้องการในบรรทัดเดียว?

คำตอบ:


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 1เป็นชวเลขลับของ awk สำหรับการพิมพ์บรรทัด

สามารถแยกเป็นสตรีมได้หลายแบบ:

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

โอ้เยี่ยมมาก! เป็นไปได้ไหมที่จะแบ่งเป็นหลาย ๆ ไฟล์โดยไม่ต้องทำ awk อื่นแทน file2? ฉันหมายถึงในวิธีที่ regexes สามารถทับซ้อนกันตัวอย่าง
yukashima huksay

1
@ aran ใช่ awk มีความยืดหยุ่นมาก แน่นอนว่ามันจะขึ้นอยู่กับว่า regexes เหลื่อมกันอย่างไร
John1024

ฉันชอบที่จะเห็นวิธีแก้ปัญหาแม้ว่าจะไม่รองรับ regexes ที่ทับซ้อนกัน การซ้อนทับกันหมายถึงการมีจุดตัดของเซตย่อยไม่ว่างเปล่า
yukashima huksay

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และทำให้สายนี้ไป

คุณช่วยเพิ่มคำอธิบายเพิ่มเติมลงในโซลูชันล่าสุดได้ไหม
yukashima huksay

@aran เพิ่มคำอธิบายแล้ว นอกจากนี้คำสั่งจะถูกแก้ไข - file_1และfile_2ถูกสลับไปยังลำดับที่ถูกต้อง
MiniMax

0

ฉันชอบ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

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.