ค้นหาไฟล์ที่มีคำค้นหาหลาย ๆ คำในไฟล์


16

ฉันกำลังมองหาวิธีที่จะแสดงรายการไฟล์ทั้งหมดในไดเรกทอรีที่มีชุดคำหลักเต็มรูปแบบที่ฉันค้นหาทุกที่ในไฟล์

ดังนั้นคำหลักไม่จำเป็นต้องปรากฏในบรรทัดเดียวกัน

วิธีหนึ่งในการทำเช่นนี้คือ:

grep -l one $(grep -l two $(grep -l three *))

คำหลักสามคำเป็นเพียงตัวอย่างมันอาจมีสองหรือสี่คำเป็นต้น

วิธีที่สองที่ฉันนึกได้คือ:

grep -l one * | xargs grep -l two | xargs grep -l three

วิธีที่สามที่ปรากฏในคำถามอื่นจะเป็น:

find . -type f \
  -exec grep -q one {} \; -a \
  -exec grep -q two {} \; -a \
  -exec grep -q three {} \; -a -print

แต่นั่นไม่ใช่ทิศทางที่ฉันไปที่นี่ ฉันต้องการบางสิ่งบางอย่างที่ต้องใช้ในการพิมพ์น้อยลงและอาจจะเป็นเพียงหนึ่งในการเรียกร้องให้grep, awk, perlหรือคล้ายกัน

ตัวอย่างเช่นฉันชอบawkให้คุณจับคู่บรรทัดที่มีคำหลักทั้งหมดเช่น:

awk '/one/ && /two/ && /three/' *

หรือพิมพ์เฉพาะชื่อไฟล์:

awk '/one/ && /two/ && /three/ { print FILENAME ; nextfile }' *

แต่ฉันต้องการค้นหาไฟล์ที่คำหลักอาจอยู่ที่ใดก็ได้ในไฟล์ไม่จำเป็นต้องอยู่ในบรรทัดเดียวกัน


โซลูชันที่ต้องการจะเป็นมิตรกับ gzip ตัวอย่างเช่นgrepมีzgrepตัวแปรที่ทำงานกับไฟล์บีบอัด ทำไมฉันพูดถึงเรื่องนี้ก็คือการแก้ปัญหาบางอย่างอาจไม่ทำงานได้ดีตามข้อ จำกัด ตัวอย่างเช่นในawkตัวอย่างของการพิมพ์ไฟล์ที่ตรงกันคุณไม่สามารถทำได้เพียง:

zcat * | awk '/pattern/ {print FILENAME; nextfile}'

คุณต้องเปลี่ยนคำสั่งเป็นอย่างมากเช่น:

for f in *; do zcat $f | awk -v F=$f '/pattern/ { print F; nextfile }'; done

ดังนั้นเนื่องจากข้อ จำกัด คุณต้องโทรawkหลายครั้งแม้ว่าคุณจะสามารถทำได้เพียงครั้งเดียวด้วยไฟล์ที่ไม่มีการบีบอัด และแน่นอนว่ามันจะดีกว่าที่จะทำzawk '/pattern/ {print FILENAME; nextfile}' *และได้รับผลกระทบแบบเดียวกันดังนั้นฉันจึงต้องการโซลูชันที่อนุญาต


1
คุณไม่ต้องการให้พวกมันเป็นgzipมิตรแค่zcatไฟล์ก่อน
terdon

@terdon ฉันแก้ไขโพสต์แล้วอธิบายว่าทำไมฉันถึงพูดถึงไฟล์ที่ถูกบีบอัด
arekolek

ไม่มีความแตกต่างระหว่างการเปิดใช้ awk ครั้งเดียวหรือหลายครั้ง ฉันหมายความว่าโอเคค่าใช้จ่ายเล็กน้อย แต่ฉันสงสัยว่าคุณจะสังเกตเห็นความแตกต่าง แน่นอนว่ามันเป็นไปได้ที่จะทำให้ awk / perl ทำสิ่งที่สคริปต์ทำเอง แต่มันเริ่มที่จะกลายเป็นโปรแกรมที่เป่าเต็มและไม่ใช่ซับไลน์อย่างรวดเร็ว นั่นคือสิ่งที่คุณต้องการ?
terdon

@terdon โดยส่วนตัวแล้วสิ่งที่สำคัญกว่าสำหรับฉันคือความซับซ้อนของคำสั่ง (ฉันเดาว่าการแก้ไขครั้งที่สองของฉันมาในขณะที่คุณกำลังแสดงความคิดเห็น) ตัวอย่างเช่นgrepโซลูชันสามารถปรับเปลี่ยนได้อย่างง่ายดายเพียงแค่นำหน้าgrepด้วย a zไม่จำเป็นต้องให้ฉันจัดการกับชื่อไฟล์ด้วย
arekolek

grepแต่ก็ใช่ว่าของ AFAIK เท่านั้นgrepและcatมี "z-variants" มาตรฐาน ฉันไม่คิดว่าคุณจะได้อะไรที่ง่ายกว่าการใช้for f in *; do zcat -f $f ...โซลูชัน สิ่งอื่นใดจะต้องเป็นโปรแกรมเต็มรูปแบบที่ตรวจสอบรูปแบบไฟล์ก่อนที่จะเปิดหรือใช้ไลบรารีเพื่อทำสิ่งเดียวกัน
terdon

คำตอบ:


13
awk 'FNR == 1 { f1=f2=f3=0; };

     /one/   { f1++ };
     /two/   { f2++ };
     /three/ { f3++ };

     f1 && f2 && f3 {
       print FILENAME;
       nextfile;
     }' *

หากคุณต้องการจัดการไฟล์ gzipped โดยอัตโนมัติให้รันไฟล์นี้ในลูปด้วยzcat(ช้าและไม่มีประสิทธิภาพเพราะคุณจะฟอร์กawkหลายครั้งในลูปหนึ่งครั้งสำหรับแต่ละชื่อไฟล์) หรือเขียนอัลกอริทึมเดียวกันในperlและใช้IO::Uncompress::AnyUncompressโมดูลห้องสมุดที่สามารถ ขยายขนาดไฟล์บีบอัดหลายชนิด (gzip, zip, bzip2, lzop) หรือในไพ ธ อนซึ่งมีโมดูลสำหรับจัดการไฟล์บีบอัด


ต่อไปนี้เป็นperlเวอร์ชันที่ใช้IO::Uncompress::AnyUncompressสำหรับอนุญาตรูปแบบจำนวนเท่าใดก็ได้และชื่อไฟล์จำนวนเท่าใดก็ได้ (มีทั้งข้อความธรรมดาหรือข้อความที่บีบอัด)

args ทั้งหมดก่อน--จะถือเป็นรูปแบบการค้นหา args ทั้งหมดหลังจากนั้น--จะถือเป็นชื่อไฟล์ การจัดการตัวเลือกดั้งเดิม แต่มีประสิทธิภาพสำหรับงานนี้ การจัดการตัวเลือกที่ดีกว่า (เช่นเพื่อสนับสนุน-iตัวเลือกสำหรับการค้นหาแบบตัวเล็ก) สามารถทำได้ด้วยโมดูลGetopt::StdหรือGetopt::Long

เรียกใช้เช่น:

$ ./arekolek.pl one two three -- *.gz *.txt
1.txt.gz
4.txt.gz
5.txt.gz
1.txt
4.txt
5.txt

(ฉันจะไม่แสดงรายการไฟล์{1..6}.txt.gzและ{1..6}.txtที่นี่ ... พวกเขามีคำบางคำหรือทั้งหมด "หนึ่ง" "สอง" "สาม" "สี่" "ห้า" และ "หก" สำหรับการทดสอบไฟล์ที่แสดงในผลลัพธ์ด้านบน มีรูปแบบการค้นหาทั้งสามแบบทดสอบด้วยตัวคุณเองด้วยข้อมูลของคุณเอง)

#! /usr/bin/perl

use strict;
use warnings;
use IO::Uncompress::AnyUncompress qw(anyuncompress $AnyUncompressError) ;

my %patterns=();
my @filenames=();
my $fileargs=0;

# all args before '--' are search patterns, all args after '--' are
# filenames
foreach (@ARGV) {
  if ($_ eq '--') { $fileargs++ ; next };

  if ($fileargs) {
    push @filenames, $_;
  } else {
    $patterns{$_}=1;
  };
};

my $pattern=join('|',keys %patterns);
$pattern=qr($pattern);
my $p_string=join('',sort keys %patterns);

foreach my $f (@filenames) {
  #my $lc=0;
  my %s = ();
  my $z = new IO::Uncompress::AnyUncompress($f)
    or die "IO::Uncompress::AnyUncompress failed: $AnyUncompressError\n";

  while ($_ = $z->getline) {
    #last if ($lc++ > 100);
    my @matches=( m/($pattern)/og);
    next unless (@matches);

    map { $s{$_}=1 } @matches;
    my $m_string=join('',sort keys %s);

    if ($m_string eq $p_string) {
      print "$f\n" ;
      last;
    }
  }
}

แฮช%patternsประกอบด้วยชุดรูปแบบที่สมบูรณ์ที่ไฟล์ต้องมีอย่างน้อยหนึ่งในสมาชิกแต่ละราย $_pstringคือสตริงที่มีคีย์ที่เรียงลำดับของแฮชนั้น สตริง$patternมีนิพจน์ทั่วไปที่รวบรวมไว้ล่วงหน้าซึ่งสร้างจาก%patternsแฮช

$patternเปรียบเทียบกับแต่ละบรรทัดของไฟล์อินพุตแต่ละไฟล์ (ใช้โมดิ/oฟายเออร์เพื่อคอมไพล์$patternเพียงครั้งเดียวเนื่องจากเรารู้ว่ามันจะไม่เปลี่ยนแปลงระหว่างการรัน) และmap()ใช้เพื่อสร้างแฮช (% s) ที่มีการจับคู่สำหรับแต่ละไฟล์

เมื่อใดก็ตามที่เห็นรูปแบบทั้งหมดในไฟล์ปัจจุบัน (โดยการเปรียบเทียบถ้า$m_string(คีย์ที่เรียงใน%s) เท่ากับ$p_string) ให้พิมพ์ชื่อไฟล์และข้ามไปที่ไฟล์ถัดไป

นี่ไม่ใช่วิธีแก้ปัญหาที่รวดเร็วโดยเฉพาะ แต่ไม่ช้าอย่างไร้เหตุผล รุ่นแรกใช้เวลา 4m58s ในการค้นหาคำสามคำในไฟล์บันทึกที่ถูกบีบอัด 74MB (รวม 937MB ที่ไม่บีบอัด) รุ่นปัจจุบันนี้ใช้เวลา 1m13s อาจมีการเพิ่มประสิทธิภาพเพิ่มเติมที่สามารถทำได้

หนึ่งในการเพิ่มประสิทธิภาพที่เห็นได้ชัดคือการใช้นี้ร่วมกับxargs's -Paka --max-procsที่จะเรียกใช้การค้นหาหลายคนบนย่อยของไฟล์ในแบบคู่ขนาน ในการทำเช่นนั้นคุณต้องนับจำนวนไฟล์และหารด้วยจำนวนแกน / cpus / เธรดที่ระบบของคุณมี (และปัดเศษขึ้นโดยการเพิ่ม 1) เช่นมีการค้นหาไฟล์ 269 ไฟล์ในชุดตัวอย่างของฉันและระบบของฉันมี 6 คอร์ (AMD 1090T) ดังนั้น:

patterns=(one two three)
searchpath='/var/log/apache2/'
cores=6
filecount=$(find "$searchpath" -type f -name 'access.*' | wc -l)
filespercore=$((filecount / cores + 1))

find "$searchpath" -type f -print0 | 
  xargs -0r -n "$filespercore" -P "$cores" ./arekolek.pl "${patterns[@]}" --

ด้วยการปรับให้เหมาะสมนั้นใช้เวลาเพียง 23 วินาทีในการค้นหาไฟล์ที่ตรงกันทั้งหมด 18 ไฟล์ แน่นอนว่าสามารถทำได้ด้วยวิธีการแก้ปัญหาอื่น ๆ หมายเหตุ: ลำดับของชื่อไฟล์ที่แสดงในเอาท์พุทจะแตกต่างกันดังนั้นอาจจำเป็นต้องเรียงลำดับหลังจากนั้นหากจำเป็น

ดังที่บันทึกไว้โดย @arekolek หลาย ๆzgreps ด้วยfind -execหรือxargsสามารถทำได้เร็วขึ้นอย่างมาก แต่สคริปต์นี้มีข้อดีของการสนับสนุนรูปแบบจำนวนเท่าใดก็ได้ที่จะค้นหาและมีความสามารถในการจัดการกับการบีบอัดหลายประเภท

หากสคริปต์ถูก จำกัด ให้ตรวจสอบเพียง 100 บรรทัดแรกของแต่ละไฟล์สคริปต์จะดำเนินการผ่านทั้งหมด (ในตัวอย่าง 74MB ของฉันที่ 269 ไฟล์) ใน 0.6 วินาที ถ้าเรื่องนี้จะเป็นประโยชน์ในบางกรณีก็อาจจะทำให้เป็นตัวเลือกบรรทัดคำสั่ง (เช่น-l 100) แต่มันก็มีความเสี่ยงจากการไม่ได้หาทุกไฟล์ที่ตรงกัน


BTW อ้างอิงจากหน้า man สำหรับIO::Uncompress::AnyUncompressรูปแบบการบีบอัดที่รองรับคือ:


การเพิ่มประสิทธิภาพครั้งสุดท้าย (ฉันหวังว่า) โดยใช้PerlIO::gzipโมดูล (บรรจุเป็นเดเบียนเป็นlibperlio-gzip-perl) แทนIO::Uncompress::AnyUncompressฉันได้เวลาลงไปประมาณ3.1 วินาทีสำหรับการประมวลผลล็อกไฟล์ 74MB ของฉัน นอกจากนั้นยังมีการปรับปรุงบางขนาดเล็กโดยใช้แฮชที่เรียบง่ายมากกว่าSet::Scalar(ซึ่งยังบันทึกไม่กี่วินาทีกับIO::Uncompress::AnyUncompressรุ่น)

PerlIO::gzipได้รับการแนะนำให้เป็น gunzip ที่เร็วที่สุดใน/programming//a/1539271/137158 (พบกับการค้นหาโดย google perl fast gzip decompress)

ใช้xargs -Pกับสิ่งนี้ไม่ได้ปรับปรุงเลย ในความเป็นจริงมันก็ดูเหมือนว่าจะชะลอตัวลงได้ทุกที่ตั้งแต่ 0.1 ถึง 0.7 วินาที (ฉันลองสี่วิ่งและระบบของฉันทำสิ่งอื่น ๆ ในพื้นหลังซึ่งจะเปลี่ยนเวลา)

ราคาคือสคริปต์เวอร์ชันนี้สามารถรองรับไฟล์ gzipped และ uncompressed เท่านั้น ความเร็วเทียบกับความยืดหยุ่น: 3.1 วินาทีสำหรับรุ่นนี้เทียบกับ 23 วินาทีสำหรับIO::Uncompress::AnyUncompressรุ่นที่มีเครื่องxargs -Pห่อหุ้ม (หรือไม่มี 1m13s xargs -P)

#! /usr/bin/perl

use strict;
use warnings;
use PerlIO::gzip;

my %patterns=();
my @filenames=();
my $fileargs=0;

# all args before '--' are search patterns, all args after '--' are
# filenames
foreach (@ARGV) {
  if ($_ eq '--') { $fileargs++ ; next };

  if ($fileargs) {
    push @filenames, $_;
  } else {
    $patterns{$_}=1;
  };
};

my $pattern=join('|',keys %patterns);
$pattern=qr($pattern);
my $p_string=join('',sort keys %patterns);

foreach my $f (@filenames) {
  open(F, "<:gzip(autopop)", $f) or die "couldn't open $f: $!\n";
  #my $lc=0;
  my %s = ();
  while (<F>) {
    #last if ($lc++ > 100);
    my @matches=(m/($pattern)/ogi);
    next unless (@matches);

    map { $s{$_}=1 } @matches;
    my $m_string=join('',sort keys %s);

    if ($m_string eq $p_string) {
      print "$f\n" ;
      close(F);
      last;
    }
  }
}

for f in *; do zcat $f | awk -v F=$f '/one/ {a++}; /two/ {b++}; /three/ {c++}; a&&b&&c { print F; nextfile }'; doneทำงานได้ดี แต่จริง ๆ แล้วใช้เวลา 3 เท่าในgrepการแก้ปัญหาของฉันและมีความซับซ้อนมากขึ้น
arekolek

1
OTOH สำหรับไฟล์ข้อความธรรมดามันจะเร็วขึ้น และอัลกอริทึมแบบเดียวกันนี้ถูกนำไปใช้ในภาษาที่รองรับการอ่านไฟล์บีบอัด (เช่น perl หรือ python) ตามที่ฉันแนะนำว่าจะเร็วกว่า greps หลายตัว "แทรกซ้อน" เป็นเพียงบางส่วน - ส่วนตัวแล้วฉันคิดว่าสคริปต์ awk หรือ perl หรือ python เดียวนั้นซับซ้อนน้อยกว่า greps หลายตัวที่มีหรือไม่มี find .... @ คำตอบของ terdon นั้นดีและทำโดยไม่ต้องใช้โมดูลที่ฉันพูดถึง (แต่ ค่าใช้จ่ายของฟอร์ก zcat สำหรับแฟ้ม compresssed ทุกครั้ง)
cas

ฉันต้องapt-get install libset-scalar-perlใช้สคริปต์ แต่ดูเหมือนจะไม่สิ้นสุดในเวลาที่เหมาะสม
arekolek

ไฟล์ที่คุณกำลังค้นหามีขนาดเท่าไหร่ (บีบอัดและไม่บีบอัด) ไฟล์ขนาดกลางขนาดเล็กหลายสิบหรือร้อยไฟล์หรือไฟล์ขนาดใหญ่หลายพันไฟล์?
cas

นี่คือฮิสโตแกรมของขนาดไฟล์บีบอัด (20 ถึง 100 ไฟล์สูงสุด 50MB แต่ส่วนใหญ่ต่ำกว่า 5MB) ไม่มีการบีบอัดในลักษณะเดียวกัน แต่ด้วยขนาดคูณด้วย 10
arekolek

11

ตั้งค่าตัวคั่นเร็กคอร์ดเป็น.ดังนั้นawkจะถือว่าไฟล์ทั้งหมดเป็นหนึ่งบรรทัด:

awk -v RS='.' '/one/&&/two/&&/three/{print FILENAME}' *

ในทำนองเดียวกันกับperl:

perl -ln00e '/one/&&/two/&&/three/ && print $ARGV' *

3
เรียบร้อย โปรดทราบว่าการดำเนินการนี้จะโหลดไฟล์ทั้งหมดลงในหน่วยความจำซึ่งอาจเป็นปัญหาสำหรับไฟล์ขนาดใหญ่
terdon

ตอนแรกฉัน upvoting นี้เพราะมันดูมีแนวโน้ม แต่ฉันไม่สามารถทำงานกับไฟล์ gzipped ได้ for f in *; do zcat $f | awk -v RS='.' -v F=$f '/one/ && /two/ && /three/ { print F }'; doneไม่มีผลอะไร
arekolek

@arekolek นั่นเป็นห่วงสำหรับฉัน ไฟล์ของคุณถูก gzipped อย่างถูกต้องหรือไม่?
jimmij

@arekolek คุณต้องการzcat -f "$f"ถ้าไฟล์บางไฟล์ไม่ถูกบีบอัด
terdon

ฉันทดสอบกับไฟล์ที่ไม่มีการบีบอัดและawk -v RS='.' '/bfs/&&/none/&&/rgg/{print FILENAME}' greptest/*.txtยังไม่แสดงผลลัพธ์ใด ๆ ในขณะที่grep -l rgg $(grep -l none $(grep -l bfs greptest/*.txt))ให้ผลลัพธ์ที่คาดหวัง
arekolek

3

สำหรับไฟล์ที่ถูกบีบอัดคุณสามารถวนซ้ำแต่ละไฟล์และขยายไฟล์ก่อน จากนั้นด้วยคำตอบอื่น ๆ ในเวอร์ชันดัดแปลงเล็กน้อยคุณสามารถ:

for f in *; do 
    zcat -f "$f" | perl -ln00e '/one/&&/two/&&/three/ && exit(0); }{ exit(1)' && 
        printf '%s\n' "$f"
done

สคริปต์ Perl จะออกด้วย 0สถานะ (สำเร็จ) หากพบทั้งสามสตริง }{เป็น Perl END{}ชวเลข สิ่งใดก็ตามที่ตามมาจะถูกเรียกใช้งานหลังจากประมวลผลอินพุตทั้งหมด ดังนั้นสคริปต์จะออกด้วยสถานะการออกที่ไม่ใช่ 0 หากไม่พบสตริงทั้งหมด ดังนั้น&& printf '%s\n' "$f"จะพิมพ์ชื่อไฟล์เฉพาะเมื่อพบทั้งสาม

หรือเพื่อหลีกเลี่ยงการโหลดไฟล์ลงในหน่วยความจำ:

for f in *; do 
    zcat -f "$f" 2>/dev/null | 
        perl -lne '$k++ if /one/; $l++ if /two/; $m++ if /three/;  
                   exit(0) if $k && $l && $m; }{ exit(1)' && 
    printf '%s\n' "$f"
done

สุดท้ายหากคุณต้องการทำสิ่งทั้งหมดในสคริปต์จริงๆคุณสามารถทำได้:

#!/usr/bin/env perl

use strict;
use warnings;

## Get the target strings and file names. The first three
## arguments are assumed to be the strings, the rest are
## taken as target files.
my ($str1, $str2, $str3, @files) = @ARGV;

FILE:foreach my $file (@files) {
    my $fh;
    my ($k,$l,$m)=(0,0,0);
    ## only process regular files
    next unless -f $file ;
    ## Open the file in the right mode
    $file=~/.gz$/ ? open($fh,"-|", "zcat $file") : open($fh, $file);
    ## Read through each line
    while (<$fh>) {
        $k++ if /$str1/;
        $l++ if /$str2/;
        $m++ if /$str3/;
        ## If all 3 have been found
        if ($k && $l && $m){
            ## Print the file name
            print "$file\n";
            ## Move to the net file
            next FILE;
        }
    }
    close($fh);
}

บันทึกสคริปต์ด้านบนเป็นที่foo.plใดที่หนึ่งในตัวคุณ$PATHทำให้สามารถเรียกใช้งานได้และดำเนินการเช่นนี้:

foo.pl one two three *

2

จากการแก้ปัญหาทั้งหมดที่เสนอไปการแก้ปัญหาเริ่มแรกของฉันโดยใช้ grep เป็นวิธีที่เร็วที่สุดใน 25 วินาที ข้อเสียเปรียบคือมันน่าเบื่อที่จะเพิ่มและลบคำหลัก ดังนั้นฉันจึงเกิดสคริปต์ (ขนานนามmulti) ที่จำลองพฤติกรรม แต่อนุญาตให้เปลี่ยนไวยากรณ์:

#!/bin/bash

# Usage: multi [z]grep PATTERNS -- FILES

command=$1

# first two arguments constitute the first command
command_head="$1 -le '$2'"
shift 2

# arguments before double-dash are keywords to be piped with xargs
while (("$#")) && [ "$1" != -- ] ; do
  command_tail+="| xargs $command -le '$1' "
  shift
done
shift

# remaining arguments are files
eval "$command_head $@ $command_tail"

ดังนั้นตอนนี้เขียน multi grep one two three -- *เทียบเท่ากับข้อเสนอเดิมของฉันและทำงานในเวลาเดียวกัน ฉันยังสามารถใช้กับไฟล์บีบอัดได้อย่างง่ายดายโดยใช้zgrepเป็นอาร์กิวเมนต์แรกแทน

โซลูชั่นอื่น ๆ

ฉันยังทดลองใช้สคริปต์ Python โดยใช้สองกลยุทธ์คือการค้นหาคำหลักทุกบรรทัดและค้นหาคำหลักไฟล์ทั้งหมดด้วยคำหลัก กลยุทธ์ที่สองนั้นเร็วกว่าในกรณีของฉัน แต่มันก็ช้ากว่าแค่ใช้grepงานเสร็จใน 33 วินาที การจับคู่คำหลักทีละบรรทัดเสร็จใน 60 วินาที

#!/usr/bin/python3

import gzip, sys

i = sys.argv.index('--')
patterns = sys.argv[1:i]
files = sys.argv[i+1:]

for f in files:
  with (gzip.open if f.endswith('.gz') else open)(f, 'rt') as s:
    txt = s.read()
    if all(p in txt for p in patterns):
      print(f)

สคริปต์ที่กำหนดโดย terdonเสร็จใน 54 วินาที ที่จริงแล้วใช้เวลากำแพง 39 วินาทีเพราะโปรเซสเซอร์ของฉันเป็นดูอัลคอร์ สิ่งที่น่าสนใจเพราะสคริปต์ Python ของฉันใช้เวลาในการวอลล์ 49 วินาที (และgrep29 วินาที)

สคริปต์โดย CASล้มเหลวที่จะยุติในเวลาอันสมควรแม้ในจำนวนที่น้อยกว่าของแฟ้มที่ถูกประมวลผลด้วยgrepอายุต่ำกว่า 4 วินาทีเพื่อให้ฉันได้ฆ่ามัน

แต่awkข้อเสนอดั้งเดิมของเขาแม้ว่าจะช้ากว่าที่grepเป็นอยู่ก็มีข้อได้เปรียบที่เป็นไปได้ ในบางกรณีอย่างน้อยในประสบการณ์ของฉันเป็นไปได้ที่จะคาดหวังว่าคำหลักทั้งหมดควรปรากฏที่ใดที่หนึ่งในส่วนหัวของไฟล์หากพวกเขาอยู่ในไฟล์เลย สิ่งนี้ทำให้โซลูชันนี้เพิ่มประสิทธิภาพได้อย่างน่าทึ่ง:

for f in *; do
  zcat $f | awk -v F=$f \
    'NR>100 {exit} /one/ {a++} /two/ {b++} /three/ {c++} a&&b&&c {print F; exit}'
done

เสร็จสิ้นในหนึ่งในสี่ของวินาทีเมื่อเทียบกับ 25 วินาที

แน่นอนว่าเราอาจไม่มีข้อได้เปรียบในการค้นหาคำหลักที่เกิดขึ้นใกล้กับจุดเริ่มต้นของไฟล์ ในกรณีเช่นนี้การแก้ปัญหาโดยไม่NR>100 {exit}ใช้เวลา 63 วินาที (เวลา 50 วินาทีของกำแพง)

ไฟล์ที่ไม่บีบอัด

ไม่มีความแตกต่างอย่างมีนัยสำคัญในเวลาทำงานระหว่างgrepโซลูชันของฉันกับawkข้อเสนอcas ' ทั้งสองใช้เวลาเสี้ยววินาทีในการดำเนินการ

โปรดทราบว่าการกำหนดค่าเริ่มต้นของตัวแปรFNR == 1 { f1=f2=f3=0; }มีผลบังคับใช้ในกรณีเช่นนี้เพื่อรีเซ็ตตัวนับสำหรับทุกไฟล์ที่ประมวลผลในภายหลัง ดังนั้นโซลูชันนี้ต้องการแก้ไขคำสั่งในสามแห่งหากคุณต้องการเปลี่ยนคำหลักหรือเพิ่มคำหลักใหม่ ในอีกทางหนึ่งgrepคุณสามารถต่อท้าย| xargs grep -l fourหรือแก้ไขคำหลักที่คุณต้องการ

ข้อเสียของgrepการแก้ปัญหาที่ใช้การทดแทนคำสั่งคือมันจะหยุดถ้าที่ใดก็ได้ในห่วงโซ่ก่อนขั้นตอนสุดท้ายไม่มีไฟล์ที่ตรงกัน สิ่งนี้จะไม่ส่งผลกระทบต่อxargsตัวแปรเนื่องจากไปป์จะถูกยกเลิกเมื่อgrepส่งคืนสถานะที่ไม่เป็นศูนย์ ฉันได้อัปเดตสคริปต์ของฉันเพื่อใช้xargsดังนั้นฉันจึงไม่ต้องจัดการกับตัวเองทำให้สคริปต์นั้นง่ายขึ้น


โซลูชัน Python ของคุณอาจได้รับประโยชน์จากการผลักลูปลงไปที่เลเยอร์ C ด้วยnot all(p in text for p in patterns)
iruvar

@iruvar ขอบคุณสำหรับคำแนะนำ ฉันได้ลองแล้ว (มันnot) และมันเสร็จใน 32 วินาทีดังนั้นจึงไม่ปรับปรุงมากนัก แต่มันอ่านได้ดีกว่ามาก
arekolek

คุณสามารถใช้อาเรย์แบบเชื่อมโยงได้มากกว่า f1, f2, f3 ใน awk โดยใช้คีย์ = search-pattern, val = count
cas

@arekolek ดูเวอร์ชันล่าสุดของฉันโดยใช้มากกว่าPerlIO::gzip IO::Uncompress::AnyUncompressตอนนี้ใช้เวลาเพียง 3.1 วินาทีแทน 1m13s ในการประมวลผลล็อกไฟล์ 74MB ของฉัน
cas

BTW หากคุณเรียกใช้ก่อนหน้านี้eval $(lesspipe)(เช่นในของคุณ.profileฯลฯ ) คุณสามารถใช้lessแทนzcat -fและforwrapper วนรอบของคุณawkจะสามารถประมวลผลไฟล์ประเภทใดก็ได้ที่lessสามารถ (gzip, bzip2, xz และอื่น ๆ ) .... น้อยกว่าสามารถตรวจพบว่า stdout เป็นไปป์และเพิ่งจะส่งกระแสข้อมูลเพื่อ stdout ถ้ามันเป็น
cas

0

ตัวเลือกอื่น - ป้อนคำทีละครั้งเพื่อxargsให้สามารถเรียกใช้ได้grepกับไฟล์ xargsสามารถทำให้ตัวเองออกจากทันทีที่มีการร้องขอให้grepส่งคืนความล้มเหลวโดยกลับ255ไปที่มัน (ตรวจสอบxargsเอกสารประกอบ) แน่นอนว่าการวางไข่ของกระสุนและการฟอร์กกิ้งที่เกี่ยวข้องกับการแก้ปัญหานี้จะทำให้ช้าลงอย่างเห็นได้ชัด

printf '%s\n' one two three | xargs -n 1 sh -c 'grep -q $2 $1 || exit 255' _ file

และวนซ้ำ

for f in *; do
    if printf '%s\n' one two three | xargs -n 1 sh -c 'grep -q $2 $1 || exit 255' _ "$f"
    then
         printf '%s\n' "$f"
    fi
done

มันดูดี แต่ฉันไม่แน่ใจว่าจะใช้มันอย่างไร คืออะไร_และfile? การค้นหานี้ในหลายไฟล์จะถูกส่งเป็นอาร์กิวเมนต์และส่งคืนไฟล์ที่มีคำหลักทั้งหมดหรือไม่
arekolek

@arekolek เพิ่มเวอร์ชันลูป และสำหรับ_มันมันจะถูกส่งผ่าน$0ไปยังเชลล์ที่เกิดใหม่ - นี่จะแสดงเป็นชื่อคำสั่งในผลลัพธ์ของps- ฉันจะเลื่อนไปที่ต้นแบบที่นี่
iruvar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.