फ़ाइल के अंत से शुरुआत तक ग्रीप करें


38

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

आदेश:

tac accounting.log | grep $pattern

मुझे जो चाहिए वह देता है, लेकिन यह बहुत धीमा है क्योंकि ओएस को पहले पूरी फाइल को पढ़ना है और फिर पाइप पर भेजना है।

तो, मुझे कुछ तेज़ चाहिए जो फ़ाइल को अंतिम पंक्ति से पहली तक पढ़ सके।

जवाबों:


44

tacकेवल तभी मदद करता है जब आप पहले मैच के बाद रुकने के लिए grep -m 1(GNU मानकर grep) उपयोग करते हैं grep:

tac accounting.log | grep -m 1 foo

से man grep:

   -m NUM, --max-count=NUM
          Stop reading a file after NUM matching lines.  

आपके सवाल का, दोनों में उदाहरण में tacऔर grepका उपयोग कर तो पूरे फ़ाइल पर कार्रवाई करने की जरूरत है tacव्यर्थ की तरह है।

इसलिए, जब तक आप उपयोग grep -mनहीं करते हैं , तब तक उपयोग न करें tac, बस grepअंतिम मैच प्राप्त करने के लिए आउटपुट को पार्स करें :

grep foo accounting.log | tail -n 1 

एक अन्य दृष्टिकोण पर्ल या किसी अन्य स्क्रिप्टिंग भाषा का उपयोग करना होगा। उदाहरण के लिए (जहाँ $pattern=foo):

perl -ne '$l=$_ if /foo/; END{print $l}' file

या

awk '/foo/{k=$0}END{print k}' file

1
मैं टैक का उपयोग कर रहा हूं क्योंकि मुझे दिए गए पैटर्न का आखिरी मैच खोजने की आवश्यकता है। आपके सुझाव "grep -m1" का उपयोग करते हुए निष्पादन समय 0m0.597s से 0m0.007s \ o / तक चला जाता है। सभी का धन्यवाद!
होबनेर कोस्टा

1
@ HábnerCosta आपका बहुत स्वागत है। मैं समझता हूं कि आप क्यों उपयोग कर रहे हैं tac, मेरी बात यह थी कि यह तब तक मदद नहीं करता है जब तक आप भी उपयोग नहीं करते हैं -mक्योंकि फाइल को अभी भी दो कार्यक्रमों द्वारा पूरा पढ़ने की आवश्यकता है। अन्यथा, आप बस सभी घटनाओं को खोज सकते हैं और केवल पिछले एक को रख सकते हैं जैसा कि मैं करता हूं tail -n 1
terdon

6
आप "tac [...] को संपूर्ण फ़ाइल को संसाधित करने की आवश्यकता क्यों कहते हैं?" टैक करता है पहली चीज फ़ाइल के अंत की तलाश है और अंत से एक ब्लॉक पढ़ें। आप स्‍ट्रेस (1) के साथ इसे स्‍वयं सत्यापित कर सकते हैं। जब grep -mइसके साथ जोड़ा जाता है , तो यह काफी कुशल होना चाहिए।
०४

1
@camh जब grep -mइसके साथ संयुक्त है। ओपी उपयोग नहीं कर रहा था -mइसलिए grep और tac दोनों पूरी चीज़ को संसाधित कर रहे थे।
terdon

क्या आप awkलाइन के अर्थ पर विस्तार कर सकते हैं ?
शोपाजो डे एरिएरेस

12

कारण क्यों

tac file | grep foo | head -n 1

पहले मैच में रोक नहीं है क्योंकि बफरिंग है।

आम तौर पर, head -n 1एक पंक्ति को पढ़ने के बाद बाहर निकलता है। तो grepएक SIGPIPE प्राप्त करना चाहिए और साथ ही साथ अपनी दूसरी पंक्ति लिखते ही बाहर निकलना चाहिए।

लेकिन क्या होता है क्योंकि इसका उत्पादन एक टर्मिनल पर नहीं जा रहा है, grepयह बफर करता है। यही है, यह तब तक नहीं लिख रहा है जब तक कि यह पर्याप्त नहीं जमा हो गया है (जीएनयू ग्रीप के साथ मेरे परीक्षण में 4096 बाइट्स)।

इसका मतलब यह है कि grepडेटा के 8192 बाइट्स लिखे जाने से पहले यह बाहर नहीं निकलेगा, इसलिए संभवत: यह कुछ लाइनें हैं।

जीएनयू के साथ grep, आप इसे जल्दी से बाहर कर सकते हैं, --line-bufferedजिसके उपयोग से यह बताता है कि जैसे ही वे एक टर्मिनल पर जाते हैं या नहीं, वे लाइनों को लिखते हैं। तो grepयह दूसरी लाइन पर बाहर निकलता है।

लेकिन जीएनयू के साथ grep, आप -m 1इसके बजाय उपयोग कर सकते हैं जैसा कि @terdon ने दिखाया है, जो पहले मैच में बाहर निकलने से बेहतर है।

यदि आपका grepGNU नहीं है grep, तो आप उपयोग कर सकते हैं sedया awkइसके बजाय। लेकिन tac जीएनयू कमांड होने के नाते, मुझे संदेह है कि आपको एक सिस्टम मिलेगा tacजहां grepजीएनयू नहीं है grep

tac file | sed "/$pattern/!d;q"                             # BRE
tac file | P=$pattern awk '$0 ~ ENVIRON["P"] {print; exit}' # ERE

कुछ प्रणालियों को tail -rवही करना पड़ता है जो GNU tacकरता है।

ध्यान दें कि, नियमित (खोज योग्य) फ़ाइलों के लिए, tacऔर tail -rकुशल हैं क्योंकि वे फ़ाइलों को पीछे की ओर पढ़ते हैं, वे फ़ाइल को पूरी तरह से मेमोरी में नहीं पढ़ते हैं इससे पहले कि इसे पीछे की तरफ प्रिंट किया जा सके (जैसा कि @ एसएलएम का sed दृष्टिकोण या tacगैर-नियमित फ़ाइलों पर होगा) ।

उन प्रणालियों पर जहां न तो उपलब्ध हैं tacऔर न ही tail -rकेवल प्रोग्रामिंग भाषाओं जैसे perlया उपयोग के साथ पिछड़े-पठन को लागू करने के लिए विकल्प हैं :

grep -e "$pattern" file | tail -n1

या:

sed "/$pattern/h;$!d;g" file

लेकिन उन सभी मैचों को खोजने का मतलब है और केवल आखिरी को प्रिंट करना है।


4

यहां एक संभावित समाधान है जो पिछले से पैटर्न की पहली घटना का स्थान ढूंढेगा:

tac -s "$pattern" -r accounting.log | head -n 1

यह निम्नानुसार है -sऔर -rस्विच का उपयोग tacकरता है:

-s, --separator=STRING
use STRING as the separator instead of newline

-r, --regex
interpret the separator as a regular expression

सिवाय आप लाइन और पैटर्न के शुरू होने के बीच सब कुछ खो देंगे।
ychaouche

2

Sed का उपयोग करना

कुछ वैकल्पिक तरीकों को दिखाते हुए @ Terdon के ठीक उत्तर का उपयोग करके sed:

$ sed '1!G;h;$!d' file | grep -m 1 $pattern
$ sed -n '1!G;h;$p' file | grep -m 1 $pattern

उदाहरण

$ seq 10 > file

$ sed '1!G;h;$!d' file | grep -m 1 5
5

$ sed -n '1!G;h;$p' file | grep -m 1 5
5

पर्ल का उपयोग करना

एक बोनस के रूप में यहाँ याद करने के लिए पर्ल में थोड़ा आसान अंकन है:

$ perl -e 'print reverse <>' file | grep -m 1 $pattern

उदाहरण

$ perl -e 'print reverse <>' file | grep -m 1 5
5

1
यह (विशेष रूप से sedएक) परिमाण की तुलना में धीमी गति के कई आदेश होने की संभावना है grep 5 | tail -n1या sed '/5/h;$!d;g'। यह संभवतः बहुत अधिक मेमोरी का उपयोग करेगा। यह बहुत अधिक पोर्टेबल नहीं है क्योंकि आप अभी भी जीएनयू का उपयोग कर रहे हैं grep -m
स्टीफन चेज़लस
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.