एक फ़ाइल में लाइनों को खोजने का तेज़ तरीका जो दूसरे में नहीं है?


241

मेरे पास दो बड़ी फाइलें (फाइलनाम के सेट) हैं। प्रत्येक फ़ाइल में मोटे तौर पर 30.000 लाइनें। मैं फ़ाइल 1 में मौजूद लाइनों को खोजने का एक तेज़ तरीका खोजने की कोशिश कर रहा हूँ जो फ़ाइल 2 में मौजूद नहीं हैं।

उदाहरण के लिए, यदि यह फ़ाइल 1 है:

line1
line2
line3

और यह फ़ाइल 2 है:

line1
line4
line5

फिर मेरा परिणाम / आउटपुट होना चाहिए:

line2
line3

यह काम:

grep -v -f file2 file1

लेकिन यह मेरी बड़ी फ़ाइलों पर उपयोग किए जाने पर बहुत धीमा है।

मुझे संदेह है कि यह अंतर () का उपयोग करने का एक अच्छा तरीका है, लेकिन आउटपुट सिर्फ लाइनें होना चाहिए , और कुछ नहीं, और मुझे इसके लिए एक स्विच खोजने की आवश्यकता नहीं है।

क्या कोई मुझे यह करने का एक तेज़ तरीका खोजने में मदद कर सकता है, बैश और बेसिक लिनक्स बायनेरिज़ का उपयोग करके?

संपादित करें: अपने स्वयं के प्रश्न का पालन करने के लिए, यह सबसे अच्छा तरीका है जो मैंने अब तक अंतर का उपयोग करके पाया है ():

diff file2 file1 | grep '^>' | sed 's/^>\ //'

निश्चित रूप से, एक बेहतर तरीका होना चाहिए?


1
अगर यह तेज है तो आप इसे आजमा सकते हैं:awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
Kent

बिना तेजी की आवश्यकता: stackoverflow.com/questions/4366533/…
Ciro Santilli 郝海东 over over over

4
Grep -v -f file2 file1 के बारे में बताने के लिए धन्यवाद
राहुल प्रसाद


कम उपकरण सेट के साथ सरल तरीका:, cat file1 file2 file2 | sort | uniq --uniqueनीचे मेरा जवाब देखें।
ओन्ड्रा žयूका

जवाबों:


233

आप GNU diffआउटपुट में पुरानी / नई / अपरिवर्तित लाइनों के प्रारूपण को नियंत्रित करके इसे प्राप्त कर सकते हैं :

diff --new-line-format="" --unchanged-line-format=""  file1 file2

इसके लिए काम करने के लिए इनपुट फ़ाइलों को सॉर्ट किया जाना चाहिए । के साथ bashऔर zsh) आप प्रक्रिया स्थानापन्न के साथ इन-प्लेस को सॉर्ट कर सकते हैं <( ):

diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)

उपरोक्त नई और अपरिवर्तित लाइनें दबा दी गई हैं, इसलिए केवल परिवर्तित (यानी आपके मामले में हटाए गए लाइनें) आउटपुट हैं। तुम भी कुछ प्रयोग कर सकते हैं diffविकल्प है कि अन्य समाधान नहीं करते हैं, इस तरह के रूप -iमामले, या विभिन्न खाली स्थान के विकल्प (अनदेखी करने के लिए -E, -b, -vआदि) में कम सख्त मिलान के लिए।


व्याख्या

विकल्प --new-line-format, --old-line-formatऔर --unchanged-line-formatआप प्रारूप विनिर्देशकों के diffसमान अंतर को printfप्रारूपित करने के तरीके को नियंत्रित करते हैं । ये विकल्प क्रमशः नए (जोड़े गए), पुराने (हटाए गए) और अपरिवर्तित लाइनों को प्रारूपित करते हैं। एक को खाली सेट करना "" उस तरह के लाइन के आउटपुट को रोकता है।

यदि आप एकीकृत अंतर प्रारूप से परिचित हैं , तो आप इसे आंशिक रूप से फिर से बना सकते हैं:

diff --old-line-format="-%L" --unchanged-line-format=" %L" \
     --new-line-format="+%L" file1 file2

%Lविनिर्देशक प्रश्न में लाइन है, और हम साथ "+" "प्रत्येक उपसर्ग -" या "", की तरह diff -u (ध्यान दें कि यह केवल outputs मतभेद, यह अभाव है --- +++और @@लाइनों प्रत्येक वर्गीकृत किया परिवर्तन के शीर्ष पर)। आप इसका उपयोग अन्य उपयोगी चीजों को करने के लिए भी कर सकते हैं जैसे संख्या प्रत्येक पंक्ति के साथ %dn


diffविधि (अन्य सुझावों के साथ commऔर join) के साथ ही उम्मीद उत्पादन का उत्पादन क्रमबद्ध , इनपुट ही आप उपयोग कर सकते हैं <(sort ...)जगह में सॉर्ट करने के लिए। यहाँ एक सरल awk(nawk) स्क्रिप्ट है (लिपियों से जुड़े-कोनोकोस्बॉक्स के उत्तर में लिपियों से प्रेरित) जो मनमाने ढंग से ऑर्डर की गई इनपुट फ़ाइलों को स्वीकार करती है, और फ़ाइल 1 में होने वाले क्रम में लापता लाइनों को आउटपुट करती है।

# output lines in file1 that are not in file2
BEGIN { FS="" }                         # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; }     # file1, index by lineno
(NR!=FNR) { ss2[$0]++; }                # file2, index by string
END {
    for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}

यह लाइन-संख्या अनुक्रमित सरणी में लाइन द्वारा फ़ाइल 1 लाइन की संपूर्ण सामग्री को संग्रहीत करता है ll1[], और लाइन-सामग्री अनुक्रमित साहचर्य सरणी में लाइन द्वारा फ़ाइल 2 लाइन की संपूर्ण सामग्री ss2[]। दोनों फाइलों को पढ़ने के बाद, यह निर्धारित करने के लिए कि फाइल 1 में लाइन फाइल 2 में मौजूद है या नहीं, ऑपरेटर को इटरेट ll1करें और उसका उपयोग करें in। (यह diffडुप्लिकेट होने पर विधि के लिए अलग आउटपुट होगा।)

इस घटना में कि फाइलें पर्याप्त रूप से बड़ी हैं, उन दोनों को संग्रहीत करने से मेमोरी समस्या होती है, आप केवल फ़ाइल 1 को स्टोर करके मेमोरी के लिए सीपीयू का व्यापार कर सकते हैं और फाइल के पढ़ने के साथ ही मैच को हटा सकते हैं।

BEGIN { FS="" }
(NR==FNR) {  # file1, index by lineno and string
  ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) {  # file2
  if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
  for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}

उपर्युक्त फ़ाइल संख्या की पूरी सामग्री को दो सरणियों में संग्रहीत करता है, एक पंक्ति संख्या ll1[]द्वारा अनुक्रमित, एक पंक्ति सामग्री द्वारा अनुक्रमित ss1[]। तब जैसा कि file2 पढ़ा जाता है, प्रत्येक मिलान रेखा से ll1[]और हटा दिया जाता है ss1[]। अंत में फ़ाइल 1 से शेष लाइनें आउटपुट हैं, मूल क्रम को संरक्षित करते हुए।

इस स्थिति में, बताई गई समस्या के साथ, आप GNU का उपयोग करके विभाजित और जीत भी सकते हैं split(फ़िल्टरिंग एक GNU एक्सटेंशन है), फ़ाइल 1 के विखंडन के साथ बार-बार चलता है और फ़ाइल 2 को पूरी तरह से पढ़ता है:

split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1

कमांड लाइन पर -अर्थ के उपयोग और प्लेसमेंट stdinपर ध्यान दें gawk। यह split20000 लाइन प्रति-आह्वान के अंशों में फ़ाइल 1 से प्रदान किया गया है।

गैर जीएनयू सिस्टम पर उपयोगकर्ताओं के लिए, वहाँ है लगभग निश्चित रूप से एक GNU coreutils पैकेज आप प्राप्त कर सकते हैं के हिस्से के रूप पर OSX सहित एप्पल Xcode उपकरण जो जीएनयू प्रदान करता है diff, awkहै, हालांकि केवल एक POSIX / बीएसडी splitके बजाय एक जीएनयू संस्करण।


1
यह वही है जो मुझे चाहिए, समय के एक छोटे से अंश में विशाल grep द्वारा लिया जाता है। धन्यवाद!
Niels2000


हम में से कुछ ग्नू [OS X bsd यहाँ ...] पर नहीं हैं :)
rogerdpack

1
मुझे लगता है कि आप के लिए इसका मतलब है diff: सामान्य रूप से इनपुट फाइलें अलग-अलग होंगी, 1 diffउस मामले में वापस आ जाएगी। इसे बोनस मानें ;-) यदि आप शेल स्क्रिप्ट 0 और 1 में परीक्षण कर रहे हैं, तो अपेक्षित निकास कोड हैं, 2 समस्या का संकेत देता है।
मर्सपुराटिक

1
@ mr.spuratic आह हाँ, अब मुझे यह पता चला है man diff। धन्यवाद!
१३:१us पर आर्कियोसोएडेरस

246

कॉम आदेश (लघु "आम" के लिए) उपयोगी हो सकता हैcomm - compare two sorted files line by line

#find lines only in file1
comm -23 file1 file2 

#find lines only in file2
comm -13 file1 file2 

#find lines common to both files
comm -12 file1 file2 

manफ़ाइल वास्तव में इस के लिए काफी पठनीय है।


6
OSX पर निर्दोष रूप से काम करता है।
पिसरुक

41
सॉर्ट किए गए इनपुट की आवश्यकता को संभवतः हाइलाइट किया जाना चाहिए।
ट्रिपलए

21
commइनपुट को सत्यापित करने का एक विकल्प भी है, --check-order(जो कि वैसे भी ऐसा लगता है, लेकिन यह विकल्प इसे जारी रखने के बजाय त्रुटि का कारण बनेगा)। लेकिन फ़ाइलों को सॉर्ट करने के लिए, बस करो: com -23 <(sort file1) <(sort file2)और इसी तरह
माइकल

मैं एक ऐसी फ़ाइल की तुलना कर रहा था जो लिनक्स में उत्पन्न हुई फ़ाइल के खिलाफ विंडोज में उत्पन्न हुई थी और ऐसा लगता था कि commयह बिल्कुल भी काम नहीं कर रही थी। मुझे यह पता लगाने में थोड़ा समय लगा कि यह लाइन एंडिंग के बारे में है: यहां तक ​​कि समान दिखने वाली रेखाओं को अलग-अलग माना जाता है यदि उनके पास अलग-अलग लाइन एंडिंग हैं। कमांड dos2unixका उपयोग केवल CRLF लाइन एंडिंग को LF में बदलने के लिए किया जा सकता है।
ज़ीरोने

23

सुझाव दिया konsolebox की तरह, पोस्टर grep समाधान

grep -v -f file2 file1

वास्तव में महान (तेज़) काम करता है यदि आप केवल -Fविकल्प जोड़ते हैं , तो पैटर्न को नियमित अभिव्यक्तियों के बजाय निश्चित तारों के रूप में व्यवहार करें। मैंने इसे ~ 1000 लाइन फ़ाइल सूचियों की एक जोड़ी पर सत्यापित किया जिसकी मुझे तुलना करनी थी। इसके साथ -F0.031 सेकेंड (वास्तविक) लिया, जबकि इसके बिना 2.278 सेकेंड (वास्तविक) लिया, जब grep आउटपुट को रीडायरेक्ट किया गया wc -l

इन परीक्षणों में -xस्विच भी शामिल था , जो कि उन मामलों में पूरी तरह से सटीकता सुनिश्चित करने के लिए समाधान का आवश्यक हिस्सा हैं, जहां फ़ाइल 2 में वे रेखाएँ हैं, जो फ़ाइल के भाग से मेल खाती हैं, लेकिन फ़ाइल 1 में एक या अधिक लाइनें नहीं हैं।

तो एक ऐसा समाधान जिसके लिए इनपुट को क्रमबद्ध करने की आवश्यकता नहीं होती है, वह तेज़, लचीला (केस सेंसिटिविटी आदि) है:

grep -F -x -v -f file2 file1

यह grep के सभी संस्करणों के साथ काम नहीं करता है, उदाहरण के लिए यह macOS में विफल रहता है, जहां फ़ाइल 1 में एक पंक्ति को फ़ाइल 2 में मौजूद नहीं के रूप में दिखाया जाएगा, भले ही यह दूसरी पंक्ति से मेल खाता हो, जो इसका एक विकल्प है । वैकल्पिक रूप से आप इस समाधान का उपयोग करने के लिए macOS पर GNU grep स्थापित कर सकते हैं।


हाँ, यह काम करता है, लेकिन इसके साथ भी -Fयह अच्छा नहीं है।
मोलोमबी

यह इतना तेज़ नहीं है, मैंने
हार मानने

वास्तव में, यह तरीका अभी भी कम करने के तरीके से धीमा है, क्योंकि यह कोई भी अनसोल्ड फाइल्स को हैंडल कर सकता है, इसलिए इसे अनसुना करके नीचे ले जाया जाता है, कॉम छँटाई का फायदा
उठाता है

@workplaylifecycle आपको छँटाई के लिए समय जोड़ने की ज़रूरत है जो कि बहुत बड़ी के लिए अड़चन हो सकती है file2
शाम

हालांकि, -xविकल्प के साथ grep जाहिरा तौर पर अधिक मेमोरी का उपयोग करता है। file26-10 बाइट्स वाले 180M शब्दों के साथ मेरी प्रक्रिया Killedएक 32GB रैम मशीन पर हुई ...
rwst

11

whats की गति की तरह और अलग?

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted

1
अलग-अलग करने से पहले फ़ाइलों को क्रमबद्ध करने की आवश्यकता के बारे में याद दिलाने के लिए धन्यवाद। सॉर्ट + फ़ॉर MUCH तेज़ है।
Niels2000

4
एक लाइनर ;-) भिन्न <(सॉर्ट करें
फ़ाइल -1

11

यदि आप "फैंसी टूल" से कम हैं, उदाहरण के लिए कुछ न्यूनतम लिनक्स वितरण में, बस के साथ एक समाधान है cat, sortऔर uniq:

cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

परीक्षा:

seq 1 1 7 | sort --random-sort > includes.txt
seq 3 1 9 | sort --random-sort > excludes.txt
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique

# Output:
1
2    

की तुलना में यह भी अपेक्षाकृत तेज है grep


1
नोट - कुछ कार्यान्वयन --uniqueविकल्प को नहीं पहचानेंगे । आपको इसके लिए मानकीकृत POSIX विकल्प का उपयोग करने में सक्षम होना चाहिए :| uniq -u
एंड्रयूएफ

1
उदाहरण में, "2" कहां से आया?
Niels2000

1
@ Niels2000, seq 1 1 7 1 से संख्या बनाता है, वेतन वृद्धि 1 के साथ, 7 तक, अर्थात 1 2 3 4 5 6 7. और वहीं आपका 2 है!
एरिक लिग्रे

5
$ join -v 1 -t '' file1 file2
line2
line3

यह -tसुनिश्चित करता है कि यह पूरी लाइन की तुलना करता है, अगर आपके पास कुछ लाइनों में एक स्थान था।


जैसे comm, joinआप जिस इनपुट ऑपरेशन पर कार्य कर रहे हैं, उस पर दोनों इनपुट लाइनों को सॉर्ट करने की आवश्यकता है।
ट्रिपलए

4

आप पायथन का उपयोग कर सकते हैं:

python -c '
lines_to_remove = set()
with open("file2", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("f1", "r") as f:
    for line in f.readlines():
        if line.strip() not in lines_to_remove:
            print(line.strip())
'

4

का प्रयोग करें combineसे moreutilsपैकेज, एक सेट उपयोगिता है कि समर्थन करता है not, and, or, xorसंचालन

combine file1 not file2

यानी मुझे ऐसी लाइनें दें जो फाइल 1 में हों लेकिन फाइल 2 में न हों

या मुझे फ़ाइल 2 में फ़ाइल 1 माइनस लाइनों में लाइनें दें

नोट: combine किसी भी ऑपरेशन को करने से पहले दोनों फ़ाइलों में सॉर्ट करता है और अद्वितीय लाइनें ढूंढता है लेकिन diffऐसा नहीं करता है। तो आप diffऔर के उत्पादन के बीच अंतर पा सकते हैं combine

तो वास्तव में आप कह रहे हैं

File1 और file2 में अलग-अलग लाइनें ढूंढें और फिर मुझे file2 में फ़ाइल 1 माइनस लाइनों में लाइनें दें

मेरे अनुभव में, यह अन्य विकल्पों की तुलना में बहुत तेज है


2

Frerep या grep में -F विकल्प जोड़ने से मदद मिल सकती है। लेकिन तेजी से गणना के लिए आप Awk का उपयोग कर सकते हैं।

आप इनमें से किसी एक विधि को आज़मा सकते हैं:

http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219


2
+1 यह एकमात्र उत्तर है जिसे इनपुट करने की आवश्यकता नहीं है। जबकि जाहिरा तौर पर ओपी उस आवश्यकता से खुश था, यह कई वास्तविक दुनिया के परिदृश्यों में अस्वीकार्य बाधा है।
ट्रिपल

1

जिस तरह से मैं आमतौर पर ऐसा करता हूं वह --suppress-common-linesध्वज का उपयोग कर रहा है , हालांकि ध्यान दें कि यह केवल तभी काम करता है यदि आपका पक्ष साइड-बाय फॉर्मेट में करता है।

diff -y --suppress-common-lines file1.txt file2.txt


0

मैंने पाया कि मेरे लिए एक सामान्य अगर और लूप स्टेटमेंट के लिए पूरी तरह से काम किया गया है।

for i in $(cat file2);do if [ $(grep -i $i file1) ];then echo "$i found" >>Matching_lines.txt;else echo "$i missing" >>missing_lines.txt ;fi;done

2
DontReadLinesWithFor देखें । इसके अलावा, यदि आपका कोई भी grepपरिणाम कई शब्दों में विस्तारित होता है, या यदि आपकी किसी भी file2प्रविष्टि को शेल द्वारा एक ग्लोब के रूप में माना जा सकता है , तो यह कोड बहुत बुरा व्यवहार करेगा ।
चार्ल्स डफी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.