बिल्ली एक बड़ी फाइल पर लाइन वाई के लिए एक्स लाइन


132

मान लें कि मेरे पास एक विशाल पाठ फ़ाइल है (> 2GB) और मैं बस catलाइनों Xको Y(जैसे 57890000 से 57890010) चाहता हूँ।

मैं क्या समझ से मैं पाइप करके ऐसा कर सकते headमें tailया viceversa, यानी

head -A /path/to/file | tail -B

या वैकल्पिक रूप से

tail -C /path/to/file | head -D

जहां A, B, Cऔर Dफ़ाइल में लाइनों की संख्या से गणना की जा सकती, Xऔर Y

लेकिन इस दृष्टिकोण के साथ दो समस्याएं हैं:

  1. आप की गणना करने के लिए है A, B, Cऔर D
  2. कमांड pipeएक दूसरे को और भी कई पंक्तियों को पढ़ने में दिलचस्पी ले सकते हैं (जैसे कि अगर मैं किसी विशाल फ़ाइल के बीच में सिर्फ कुछ पंक्तियाँ पढ़ रहा हूँ)

वहाँ एक तरीका है कि खोल के साथ काम करना है और मैं चाहता हूँ लाइनों का उत्पादन? (केवल प्रदान करते समय Xऔर Y)?


1
FYI करें, मेरे उत्तर में जोड़े गए 6 तरीकों की वास्तविक गति परीक्षण तुलना।
केविन

जवाबों:


119

मैं sedसमाधान का सुझाव देता हूं , लेकिन पूर्णता के लिए,

awk 'NR >= 57890000 && NR <= 57890010' /path/to/file

अंतिम पंक्ति के बाद कटौती करने के लिए:

awk 'NR < 57890000 { next } { print } NR == 57890010 { exit }' /path/to/file

गति परीक्षण:

  • 100,000,000-लाइन फ़ाइल द्वारा उत्पन्न seq 100000000 > test.in
  • रीडिंग लाइन्स 50,000,000-50,000,010
  • बिना किसी विशेष क्रम के टेस्ट
  • realसमय के रूप में द्वारा रिपोर्ट bash'निर्मित एसtime
 4.373  4.418  4.395    tail -n+50000000 test.in | head -n10
 5.210  5.179  6.181    sed -n '50000000,50000010p;57890010q' test.in
 5.525  5.475  5.488    head -n50000010 test.in | tail -n10
 8.497  8.352  8.438    sed -n '50000000,50000010p' test.in
22.826 23.154 23.195    tail -n50000001 test.in | head -n10
25.694 25.908 27.638    ed -s test.in <<<"50000000,50000010p"
31.348 28.140 30.574    awk 'NR<57890000{next}1;NR==57890010{exit}' test.in
51.359 50.919 51.127    awk 'NR >= 57890000 && NR <= 57890010' test.in

ये किसी भी तरह से सटीक मानदंड नहीं हैं, लेकिन यह अंतर स्पष्ट और दोहराए जाने योग्य पर्याप्त है * इनमें से प्रत्येक आदेश की सापेक्ष गति का एक अच्छा अर्थ देने के लिए।

* - पहले दो को छोड़कर, sed -n p;qऔर head|tail, जो मूल रूप से समान हैं।


11
जिज्ञासा से बाहर: आपने परीक्षणों के बीच डिस्क कैश को कैसे बहाया है?
पावेल रुमियन

2
इसके बारे में क्या tail -n +50000000 test.in | head -n10, जो इसके विपरीत tail -n-50000000 test.in | head -n10सही परिणाम देगा?
गाइल्स

4
ठीक है, मैंने जाकर कुछ बेंचमार्क किए। पूंछ | सिर सेड की तुलना में तेज़ है, अंतर मेरी अपेक्षा बहुत अधिक है।
गाइल्स

3
@ गिल्स तुम सही हो, मेरा बुरा। tail+|headसेड से 10-15% तेज है, मैंने वह बेंचमार्क जोड़ा है।
केविन

1
मुझे पता है कि सवाल लाइनों के लिए पूछता है, लेकिन अगर आप -cवर्णों को छोड़ने के लिए उपयोग करते हैं , tail+|headतात्कालिक है। बेशक, आप "50000000" नहीं कह सकते हैं और उस अनुभाग की शुरुआत को मैन्युअल रूप से खोज सकते हैं जिसे आप खोज रहे हैं।
डैनी किर्कमीयर

51

यदि आप X को Y सम्मिलित करना चाहते हैं (1 पर क्रमांकन शुरू कर रहे हैं), का उपयोग करें

tail -n +$X /path/to/file | head -n $((Y-X+1))

tailपहली X-1 लाइनों को पढ़ेगा और छोड़ देगा (इसके आसपास कोई रास्ता नहीं है), फिर निम्न पंक्तियों को पढ़ें और प्रिंट करें। headपढ़ी जाएगी और लाइनों की अनुरोधित संख्या प्रिंट करें, फिर बाहर निकलें। जब headबाहर निकलता है, tailएक SIGPIPE सिग्नल प्राप्त करता है और मर जाता है, तो यह इनपुट फ़ाइल से लाइनों के बफर आकार (आमतौर पर कुछ किलोबाइट) से अधिक नहीं पढ़ा होगा।

वैकल्पिक रूप से, जैसा कि गोर्की ने सुझाव दिया था, सेड का उपयोग करें:

sed -n -e "$X,$Y p" -e "$Y q" /path/to/file

हालांकि, सॉल्यूशन सॉल्यूशन काफी धीमा है (कम से कम जीएनयू यूटिलिटीज और बिजीबॉक्स यूटिलिटीज के लिए; सीड अधिक प्रतिस्पर्धी हो सकता है यदि आप किसी ओएस पर फाइल का बड़ा हिस्सा निकालते हैं जहां पाइपिंग स्लो है और सेड तेज है)। यहां लिनक्स के तहत त्वरित बेंचमार्क हैं; डेटा द्वारा उत्पन्न किया गया था seq 100000000 >/tmp/a, वातावरण लिनक्स / amd64 है, /tmptmpfs है और मशीन अन्यथा निष्क्रिय है और स्वैपिंग नहीं है।

real  user  sys    command
 0.47  0.32  0.12  </tmp/a tail -n +50000001 | head -n 10 #GNU
 0.86  0.64  0.21  </tmp/a tail -n +50000001 | head -n 10 #BusyBox
 3.57  3.41  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #GNU
11.91 11.68  0.14  sed -n -e '50000000,50000010 p' -e '50000010q' /tmp/a #BusyBox
 1.04  0.60  0.46  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #GNU
 7.12  6.58  0.55  </tmp/a tail -n +50000001 | head -n 40000001 >/dev/null #BusyBox
 9.95  9.54  0.28  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #GNU
23.76 23.13  0.31  sed -n -e '50000000,90000000 p' -e '90000000q' /tmp/a >/dev/null #BusyBox

यदि आप उस बाइट रेंज को जानते हैं जिसके साथ आप काम करना चाहते हैं, तो आप इसे सीधे शुरुआत की स्थिति में छोड़ कर तेजी से निकाल सकते हैं। लेकिन लाइनों के लिए, आपको शुरुआत से पढ़ना होगा और नए अंक गिनने होंगे। ब्लॉक को एक्सक्लूसिव से y में एक्सक्लूसिव से निकालने के लिए 0 से शुरू करके, b के ब्लॉक आकार के साथ:

dd bs=$b seek=$x count=$((y-x)) </path/to/file

1
क्या आप सुनिश्चित हैं कि कोई कैबिंग इनबेटन नहीं है? पूंछ और सिर के बीच का अंतर मुझे बहुत बड़ा लगता है।
पावेल रोमियन

@gorkypl मैंने कई उपाय किए और समय तुलनीय था। जैसा कि मैंने लिखा है, यह सब रैम में हो रहा है (सब कुछ कैश में है)।
गाइल्स

1
@ गिल्स tail will read and discard the first X-1 lineको तब लगता है जब लाइनों की संख्या अंत से दी जाती है, इस तरह के मामले में, पूंछ निष्पादित समय के अनुसार अंत से पीछे की ओर पढ़ने लगती है। कृपया पढ़ें http://unix.stackexchange.com/a/216614/79743:।

1
@BinaryZebra हाँ, यदि इनपुट एक नियमित फ़ाइल है, तो tail(जीएनयू टेल सहित) के कुछ कार्यान्वयन में अंत से पढ़ने के लिए आंकड़े हैं । यह tail | headअन्य तरीकों की तुलना में समाधान में सुधार करता है ।
गिल्स

22

यह करने के लिए head | tailदृष्टिकोण सबसे अच्छा और सबसे "मुहावरेदार" तरीकों में से एक है:

X=57890000
Y=57890010
< infile.txt head -n "$Y" | tail -n +"$X"

जैसा कि टिप्पणी में गिल्स ने बताया, एक तेज़ तरीका है

< infile.txt tail -n +"$X" | head -n "$((Y - X))"

इसका कारण यह तेज है पहला एक्स - 1 लाइनों को head | tailदृष्टिकोण की तुलना में पाइप के माध्यम से जाने की आवश्यकता नहीं है ।

आपका प्रश्न जैसा कि वाक्यांशबद्ध है, थोड़ा भ्रामक है और संभवतः इस दृष्टिकोण के बारे में आपके कुछ निराधार गलतफहमियों की व्याख्या करता है।

  • आप कहते हैं कि आप की गणना करने के लिए है A, B, C, Dलेकिन जैसा कि आप देख सकते हैं, फ़ाइल की लाइन गिनती की जरूरत नहीं है और अधिक से अधिक 1 गणना के लिए आवश्यक है, जो खोल वैसे भी आप के लिए क्या कर सकते हैं।

  • आप चिंता करते हैं कि पाइपिंग आवश्यक से अधिक लाइनें पढ़ेगी। वास्तव में यह सच नहीं है: tail | headफ़ाइल I / O के संदर्भ में आप जितना कुशल हो सकते हैं, उतना ही कारगर है। सबसे पहले, आवश्यक कार्य की न्यूनतम मात्रा पर विचार करें: किसी फ़ाइल में X 'th लाइन को खोजने के लिए , इसे करने का एकमात्र सामान्य तरीका यह है कि आप हर बाइट को पढ़ें और जब आप x newline प्रतीकों को गिनें तो रोकें क्योंकि फ़ाइल को परमात्मा करने का कोई तरीका नहीं है X 'th लाइन की ऑफसेट । एक बार जब आप * X * th लाइन पर पहुँच जाते हैं, तो आपको उन्हें प्रिंट करने के लिए, Y 'th लाइन पर रुककर सभी लाइनों को पढ़ना होगा । इस प्रकार कोई भी दृष्टिकोण वाई लाइनों से कम पढ़ने के साथ दूर नहीं हो सकता है । अब, Yhead -n $Y से अधिक नहीं पढ़ता हैलाइनें (निकटतम बफर इकाई के लिए गोल है, लेकिन बफ़र्स यदि सही ढंग से उपयोग किया जाता है तो प्रदर्शन में सुधार होता है, इसलिए उस ओवरहेड के बारे में चिंता करने की कोई आवश्यकता नहीं है)। इसके अलावा, tailइससे अधिक नहीं पढ़ेंगे head, इसलिए इस प्रकार हमने दिखाया है कि head | tailसबसे कम संख्या में संभव रेखाएं (फिर से, कुछ नगण्य बफरिंग जिन्हें हम अनदेखा कर रहे हैं) पढ़ते हैं। एकल उपकरण दृष्टिकोण का एकमात्र दक्षता लाभ जो पाइप का उपयोग नहीं करता है, कम प्रक्रियाएं हैं (और इस तरह कम ओवरहेड)।


1
पुनर्निर्देशन को पहले लाइन पर पहले कभी नहीं देखा। कूल, यह पाइप के प्रवाह को साफ करता है।
क्लैक्च

14

सबसे रूढ़िवादी तरीका (लेकिन सबसे तेज़ नहीं, जैसा कि ऊपर गाइल्स ने नोट किया है) का उपयोग करना होगा sed

आपके मामले में:

X=57890000
Y=57890010
sed -n -e "$X,$Y p" -e "$Y q" filename

-nविकल्प का मतलब है कि केवल प्रासंगिक लाइनों stdout में मुद्रित कर रहे हैं।

पी लाइन नंबर परिष्करण के अंत में दी गई श्रेणी में लाइनों मुद्रित करने के लिए इसका मतलब है। क्ष स्क्रिप्ट के दूसरे भाग में फ़ाइल के शेष लंघन द्वारा कुछ समय बचाता है।


1
मुझे उम्मीद थी sedऔर tail | headबराबर के बारे में होगा, लेकिन यह पता चला है कि tail | headयह काफी तेज है ( मेरा उत्तर देखें )।
गाइल्स

1
मुझे पता नहीं, से मैं क्या पढ़ा है, tail/ head, माना जाता है एक फ़ाइल के दोनों छोर ट्रिमिंग के बाद से अधिक "रूढ़िवादी" ठीक है कि वे क्या कर रहे हैं के लिए बनाया है। उन सामग्रियों में, sedकेवल तब ही तस्वीर दर्ज करना प्रतीत होता है जब प्रतिस्थापन की आवश्यकता होती है - और जल्दी से तस्वीर से बाहर धकेल दिया जाता है जब बहुत अधिक जटिल कुछ भी होने लगता है, क्योंकि जटिल कार्यों के लिए इसका सिंटैक्स AWK की तुलना में बहुत खराब है, जो तब खत्म हो जाता है ।
अंडरस्कोर_ड

7

यदि हम पहली पंक्ति lStartसे अंतिम पंक्ति तक चयन करने की सीमा जानते हैं : lEndहम गणना कर सकते हैं:

lCount="$((lEnd-lStart+1))"

यदि हमें कुल मात्रा का पता है: lAllहम फ़ाइल के अंत की दूरी की भी गणना कर सकते हैं:

toEnd="$((lAll-lStart+1))"

तब हम दोनों को जानेंगे:

"how far from the start"            ($lStart) and
"how far from the end of the file"  ($toEnd).

इनमें से किसी एक का सबसे छोटा चयन tailnumber:

tailnumber="$toEnd"; (( toEnd > lStart )) && tailnumber="+$linestart"

हमें लगातार सबसे तेज़ निष्पादित कमांड का उपयोग करने की अनुमति देता है:

tail -n"${tailnumber}" ${thefile} | head -n${lCount}

कृपया $linestartचयनित होने पर अतिरिक्त प्लस ("+") चिह्न पर ध्यान दें ।

एकमात्र चेतावनी यह है कि हमें लाइनों की कुल गणना की आवश्यकता है, और इसे खोजने में कुछ अतिरिक्त समय लग सकता है।
जैसा कि सामान्य है:

linesall="$(wc -l < "$thefile" )"

कुछ समय मापा जाता है:

lStart |500| lEnd |500| lCount |11|
real   user   sys    frac
0.002  0.000  0.000  0.00  | command == tail -n"+500" test.in | head -n1
0.002  0.000  0.000  0.00  | command == tail -n+500 test.in | head -n1
3.230  2.520  0.700  99.68 | command == tail -n99999501 test.in | head -n1
0.001  0.000  0.000  0.00  | command == head -n500 test.in | tail -n1
0.001  0.000  0.000  0.00  | command == sed -n -e "500,500p;500q" test.in
0.002  0.000  0.000  0.00  | command == awk 'NR<'500'{next}1;NR=='500'{exit}' test.in


lStart |50000000| lEnd |50000010| lCount |11|
real   user   sys    frac
0.977  0.644  0.328  99.50 | command == tail -n"+50000000" test.in | head -n11
1.069  0.756  0.308  99.58 | command == tail -n+50000000 test.in | head -n11
1.823  1.512  0.308  99.85 | command == tail -n50000001 test.in | head -n11
1.950  2.396  1.284  188.77| command == head -n50000010 test.in | tail -n11
5.477  5.116  0.348  99.76 | command == sed -n -e "50000000,50000010p;50000010q" test.in
10.124  9.669  0.448  99.92| command == awk 'NR<'50000000'{next}1;NR=='50000010'{exit}' test.in


lStart |99999000| lEnd |99999010| lCount |11|
real   user   sys    frac
0.001  0.000  0.000  0.00  | command == tail -n"1001" test.in | head -n11
1.960  1.292  0.660  99.61 | command == tail -n+99999000 test.in | head -n11
0.001  0.000  0.000  0.00  | command == tail -n1001 test.in | head -n11
4.043  4.704  2.704  183.25| command == head -n99999010 test.in | tail -n11
10.346  9.641  0.692  99.88| command == sed -n -e "99999000,99999010p;99999010q" test.in
21.653  20.873  0.744  99.83 | command == awk 'NR<'99999000'{next}1;NR=='99999010'{exit}' test.in

ध्यान दें कि यदि चयनित रेखाएँ प्रारंभ में या अंत के पास हैं, तो समय बहुत बदल जाता है। एक कमांड जो फ़ाइल के एक तरफ अच्छी तरह से काम करती दिखाई देती है, वह फ़ाइल के दूसरी तरफ बेहद धीमी हो सकती है।


टिप्पणियाँ विस्तारित चर्चा के लिए नहीं हैं; इस वार्तालाप को बातचीत में स्थानांतरित कर दिया गया है ।
terdon

@ BinaryZebra - बेहतर तरीके से।
मिकसर्व

0

मैं अक्सर ऐसा करता हूं और इसलिए यह स्क्रिप्ट लिखी है। मुझे लाइन नंबर खोजने की जरूरत नहीं है, स्क्रिप्ट यह सब करती है।

#!/bin/bash

# $1: start time
# $2: end time
# $3: log file to read
# $4: output file

# i.e. log_slice.sh 18:33 19:40 /var/log/my.log /var/log/myslice.log

if [[ $# != 4 ]] ; then 
echo 'usage: log_slice.sh <start time> <end time> <log file> <output file>'
echo
exit;
fi

if [ ! -f $3 ] ; then
echo "'$3' doesn't seem to exit."
echo 'exiting.'
exit;
fi

sline=$(grep -n " ${1}" $3|head -1|cut -d: -f1)  #what line number is first occurrance of start time
eline=$(grep -n " ${2}" $3|head -1|cut -d: -f1)  #what line number is first occurrance of end time

linediff="$((eline-sline))"

tail -n+${sline} $3|head -n$linediff > $4

2
आप एक सवाल का जवाब दे रहे हैं जो नहीं पूछा गया था। आपका उत्तर 10% है tail|head, जिस पर प्रश्न और अन्य उत्तरों में बड़े पैमाने पर चर्चा की गई है, और 90% लाइन संख्या निर्धारित करते हैं जहां निर्दिष्ट स्ट्रिंग्स / पैटर्न दिखाई देते हैं, जो सवाल का हिस्सा नहीं था । पुनश्च आपको अपने शेल मापदंडों और चर को हमेशा उद्धृत करना चाहिए; उदाहरण के लिए, "$ 3" और "$ 4"।
जी-मैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.