„Bash“ įrankis, skirtas n. Eilutės išvedimui iš failo

Ar yra „kanoninis“ būdas tai padaryti? Aš naudoju head -n | tail -1 head -n | tail -1 , kuri daro triuką, bet man buvo įdomu, ar yra „bash“ įrankis, kuris konkrečiai ištraukia eilutę (arba linijų diapazoną) iš failo.

„Kanoniniu“ reiškia programą, kurios pagrindinė funkcija yra vykdoma.

446
16 мая '11 в 22:33 2011-05-16 22:33 Vladas Vivdovitchas paklausė gegužės 16 d., 11 val. 10.33 val. 2011-05-16 22:33
@ 19 atsakymų

head , ir vamzdis su tail bus lėtas didžiuliam failui. Siūlyčiau sekti taip:

 sed 'NUMq;d' file 

Kur NUM yra eilutės numeris, kurį norite spausdinti; pvz., sed '10q;d' file skirtas 10 eilutės file spausdinimui.

Paaiškinimas:

NUMq bus nedelsiant sustabdomas, kai eilutės numeris yra NUM .

d panaikins eilutę, o ne spausdins; jis užblokuotas paskutinėje eilutėje, nes q išeina iš likusios scenarijaus dalies.

Jei kintamajame yra NUM , turite naudoti dvigubas kabutes vietoj atskirų kabučių:

 sed "${NUM}q;d" file 
598
16 мая '11 в 22:38 2011-05-16 22:38 atsakymą pateikė anubhava gegužės 16 d. 11 val. 22:38 2011-05-16 22:38
 sed -n '2p' < file.txt 

spausdins antrą eilutę

 sed -n '2011p' < file.txt 

2011 m. Eilutė

 sed -n '10,33p' < file.txt 

10 eilutė nuo 33 linijos

 sed -n '1p;3p' < file.txt 

1 ir 3 eilutės

ir pan

Jei norite pridėti eilutes su „sed“, galite tai patikrinti:

sed: įterpti eilutę konkrečioje padėtyje

235
16 мая '11 в 22:39 2011-05-16 22:39 atsakymas pateikiamas jm666 gegužės 16 d., 10:39, 2011-05-16 22:39

Turiu unikalią situaciją, kurioje galiu išbandyti siūlomus sprendimus šiame puslapyje, todėl rašau šį atsakymą kaip siūlomų sprendimų konsolidavimą su kiekvienu pateiktu vykdymo laiku.

Tinkinimas

Turiu ASCII teksto duomenų failą, kuriame yra 3,261 gigabaitų, kiekvienai eilutei - viena pagrindinių verčių pora. Faile yra 3,339,550,320 eilučių apskritai ir iššūkis atidarymui bet kuriame bandomame redaktoriuje, įskaitant mano Vim. Turiu padauginti šį failą, kad galėčiau išnagrinėti kai kurias vertes, kurias radau, pradedant maždaug 500 000 000 linijų.

Kadangi failas turi tiek daug eilučių:

  • Noriu padaryti kažką naudingo su duomenimis, turiu išskirti tik eilių pogrupį.
  • Kiekvienos eilutės, skaitančios prieš mane sužadinančias vertybes, skaitymas užtruks ilgai.
  • Jei sprendimas nuskaito paskutines eilutes, kurias aš rūpinuosi ir toliau perskaitysiu likusią failo dalį, jis praleis laiką, skaitant beveik 3 milijardus nereikšmingų linijų, ir 6 kartus daugiau nei reikės.

Mano geriausias scenarijus yra sprendimas, kuris tik ištraukia vieną eilutę iš failo neskaitant jokių kitų failo eilučių, bet negaliu įsivaizduoti, kaip tai padaryti Bash'e.

Mano proto labui aš neskaitysiu visų 500 000 000 linijų, kurias man reikia savo problemai spręsti. Vietoj to, bandysiu ištraukti 50 000 000 linijų iš 3,339,550,320 (tai reiškia, kad viso failo skaitymas užtruks 60 kartų daugiau nei reikia).

Kiekvieną komandą išbandysiu naudodamiesi įmontuotu time .

Pradinė padėtis

Pirmiausia apsvarstykite, kaip head tail sprendimas:

 $ time head -50000000 myfile.ascii | tail -1 pgm_icnt = 0 real 1m15.321s 

50 milijonų linijos pradinė linija yra 00: 01: 15.321, jei aš einu tiesiai 500 milijonų, tai tikriausiai būtų ~ 12,5 minučių.

iškirpti

Aš abejoju, bet tai verta:

 $ time cut -f50000000 -d$'\n' myfile.ascii pgm_icnt = 0 real 5m12.156s 

Tai užtruko 00: 05: 12,156, kuris yra daug lėtesnis nei pradinis! Aš nesu įsitikinęs, ar jis perskaitė visą failą, arba tik iki 50 mln. JAV dolerių iki sustojimo, bet nesvarbu, kad jis neatrodo perspektyvus problemos sprendimas.

Awk

Tiesiog paleidžiau sprendimą exit , nes nesitikėjau paleisti viso failo:

 $ time awk 'NR == 50000000 {print; exit}' myfile.ascii pgm_icnt = 0 real 1m16.583s 

Šis kodas prasidėjo 00: 01: 16.583, o tai yra tik 1 sekundė lėčiau, tačiau vis dar nepagerina pradinės linijos. Tuo greičiu, jei išjungimo komanda buvo atmesta, greičiausiai užtruks apie 76 minutes, kad perskaitytumėte visą failą!

Perl

Aš taip pat pradėjau esamą „Perl“ sprendimą:

 $ time perl -wnl -e '$.== 50000000  print  exit;' myfile.ascii pgm_icnt = 0 real 1m13.146s 

Šis kodas dirbo 00: 01: 13.146, o tai yra ~ 2 sekundės greičiau nei bazinė. Jei bėgau 500 000 000, tai tikriausiai užtruks apie 12 minučių.

SED

Pagrindinis atsakymas laive yra mano rezultatas:

 $ time sed "50000000q;d" myfile.ascii pgm_icnt = 0 real 1m12.705s 

Šis kodas dirbo 00: 01: 12.705, kuris yra 3 sekundės greičiau nei pradinė ir ~ 0,4 sekundės greičiau nei Perl. Jei jį išbandžiau 500 milijonų linijų, tai tikriausiai užtruks apie 12 minučių.

projekto failą

Turiu bash 3.1, todėl negaliu patikrinti mapfile sprendimo.

Išvada

Atrodo, kad didžia dalimi sunku pagerinti head tail sprendimą. Geriausiu atveju sed suteikia efektyvumo padidėjimą 3%.

(palūkanos apskaičiuojamos pagal formulę % = (runtime/baseline - 1) * 100 )

50 000 000 linija

  • 00: 01: 12,705 (-00: 00: 02,616 = -3,47%) sed
  • 00: 01: 13,146 (-00: 00: 02,177 = -2,89%) perl
  • 00: 01: 15.321 (+00: 00: 00.000 = + 0.00%) head|tail
  • 00: 01: 16,583 (+00: 00: 01,262 = + 1,68%) awk
  • 00: 05: 12 156 (+00: 03: 56,835 = + 314,43%)

Eilutė 500,000,000

  • 00: 12: 07.050 (-00: 00: 26.160) sed
  • 00: 12: 11.460 (-00: 00: 21,750) perl
  • 00: 12: 33.210 (+00: 00: 00.000) head|tail
  • 00: 12: 45,830 (+00: 00: 12,620) awk
  • 00: 52: 01.560 (+00: 40: 31.650) cut

3,338,559,320 eilutė

  • 01: 20: 54,599 (-00: 03: 05.327) sed
  • 01: 21: 24,045 (-00: 02: 25.227) perl
  • 01: 23: 49.273 (+00: 00: 00.000) head|tail
  • 01: 25: 13.548 (+00: 02: 35.735) awk
  • 05: 47: 23,026 (+04: 24: 26.246) cut
70
30 авг. Atsakymas, kurį pateikė CaffeineConnoisseur, rugpjūčio 30 d. 2016-08-30 03:29 '16 at 3:29 2016-08-30 03:29

Su awk tai gana greitai:

 awk 'NR == num_line' file 

Tokiu atveju numatytasis „ awk elgesys yra: {print $0} .


Alternatyvios versijos

Jei failas yra didžiulis, geriau exit po to, kai perskaitysite norimą eilutę. Taip sutaupote procesoriaus laiką.

 awk 'NR == num_line {print; exit}' file 

Jei norite nurodyti linijos numerį iš bazinio kintamojo, galite naudoti:

 awk 'NR == n' n=$num file awk -vn=$num 'NR == n' file # equivalent 
41
22 янв. atsakymą pateikė fedorqui Jan 22 2014-01-22 12:49 '14 at 12:49 2014-01-22 12:49

Oho, visos funkcijos!

Pabandykite atlikti šiuos veiksmus:

 sed -n "${lineNum}p" $file 

arba vienas iš jų, priklausomai nuo jūsų „awk“ versijos:

 awk -vlineNum=$lineNum 'NR == lineNum {print $0}' $file awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file 

(Gali tekti išbandyti komandą nawk arba gawk ).

Ar yra įrankis, kuris spausdina tik šią konkrečią liniją? Ne vienas iš standartinių įrankių. Tačiau sed yra tikriausiai artimiausia ir lengviausia naudoti.

26
17 мая '11 в 15:52 2011-05-17 15:52 Atsakymą davė Davidas W. gegužės 17 d., 11 val. 15:52 2011-05-17 15:52
Naudingi vieno eilutės scenarijai, skirti sed 

20
18 окт. Steven Penny atsakymas, pateiktas spalio 18 d 2012-10-18 08:54 '12 at 8:54 2012-10-18 08:54

Šis klausimas pažymėtas Bash, čia yra Bash (≥4): naudokite mapfile su galimybe -s (praleisti) ir -n (skaičius).

Jei reikia gauti 42-ąją file eilutę:

 mapfile -s 41 -n 1 ary < file 

Šiuo metu turėsite matricą, kurios laukuose yra eilutės file (įskaitant paskutinę naują eilutę), kur mes praleidome pirmąsias 41 eilutes ( -s 41 ) ir sustojome po to, kai perskaitėme vieną eilutę ( -n 1 ). Taigi 42 eilutė. Jei norite spausdinti:

 printf '%s' "${ary[0]}" 

Jei jums reikia eilių eilučių, pvz., 42–666 (imtinai) ir pasakyti, kad nenorite, kad matematika būtų patys, ir spausdinti juos standartine išvestimi:

 mapfile -s $((42-1)) -n $((666-42+1)) ary < file printf '%s' "${ary[@]}" 

Jei taip pat reikia apdoroti šias linijas, paskutinės naujos eilutės saugojimas nėra labai patogu. Tokiu atveju naudokite -t (apdailos) parinktį:

 mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file # do stuff printf '%s\n' "${ary[@]}" 

Jums gali būti funkcija:

 print_file_range() { # $1-$2 is the range of file $3 to be printed to stdout local ary mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3" printf '%s' "${ary[@]}" } 

Nėra jokių išorinių komandų, tik įterpta „Bash“!

19
17 мая '14 в 16:45 2014-05-17 16:45 Atsakymas duotas gniourf_gniourf Gegužės 17 d., „14, 16:45 2014-05-17 16:45

Taip pat galite naudoti „sed print“ ir „quit“:

 sed -n '10{p;q;}' file # print line 10 
10
17 мая '11 в 14:49 2011-05-17 14:49 atsakymas duotas berndui gegužės 17 d., 11 val. 2011-05-17 14:49

Pagal mano atliktus testus, atsižvelgiant į našumą ir įskaitomumą, mano rekomendacija yra:

tail -N+N | head -1

N yra norimas linijos numeris. Pavyzdžiui, tail -N+7 input.txt | head -1 tail -N+7 input.txt | head -1 tail -N+7 input.txt | head -1 tail -N+7 input.txt | head -1 išspausdins 7 eilutę.

tail -N+N išspausdins viską, pradedant nuo linijos N , o head -1 sustabdys ją po vienos linijos.


Pakaitinė head -N | tail -1 head -N | tail -1 head -N | tail -1 head -N | tail -1 , galbūt šiek tiek perskaityta. Pavyzdžiui, ji spausdins septintą eilutę:

head -7 input.txt | tail -1

Kai kalbama apie našumą, mažesnių dydžių skirtumas nėra didelis, bet jis viršys tail | head tail | head tail | head tail | head (viršuje), kai failai tampa didžiuliai.

Įdomiausias dalykas, kurį reikia žinoti apie „ sed 'NUMq;d' , bet norėčiau pasakyti, kad jis bus suprantamas mažiau žmonių, nei galvos / uodegos tirpalas, taip pat lėčiau nei uodegos / galvos sprendimas.

Savo bandymuose abiejų uodegų / galvučių versijos buvo pranašesnės už sed 'NUMq;d' serijas. Tai atitinka kitus paskelbtus rodiklius. Sunku rasti atvejį, kai uodegos / galvos buvo labai blogos. Tai taip pat nenuostabu, nes tai yra operacijos, kurių tikitės bus labai optimizuotos modernioje „Unix“ sistemoje.

Norėdami sužinoti apie veiklos skirtumus, tai yra numeris, kurį gaunu didžiuliam failui (9,3G):

  • tail -N+N | head -1 tail -N+N | head -1 tail -N+N | head -1 tail -N+N | head -1 : 3,7 s
  • head -N | tail -1 head -N | tail -1 head -N | tail -1 head -N | tail -1 : 4,6 s
  • sed Nq;d : 18,8 s

Rezultatai gali skirtis, tačiau našumo head | tail head | tail head | tail head | tail ir tail | head tail | head tail | head tail | head paprastai yra panaši į mažas sąnaudas, o sed visada sed lėčiau, o reikšmingas veiksnys yra apie 5 kartus.

Jei norite atkurti savo etaloną, galite išbandyti šiuos veiksmus, tačiau įspėti, kad jis sukurs 9.3G failą dabartiniame darbo kataloge:

 #!/bin/bash readonly file=tmp-input.txt readonly size=1000000000 readonly pos=500000000 readonly retries=3 seq 1 $size > $file echo "*** head -N | tail -1 ***" for i in $(seq 1 $retries) ; do time head "-$pos" $file | tail -1 done echo "-------------------------" echo echo "*** tail -n+N | head -1 ***" echo seq 1 $size > $file ls -alhg $file for i in $(seq 1 $retries) ; do time tail -n+$pos $file | head -1 done echo "-------------------------" echo echo "*** sed Nq;d ***" echo seq 1 $size > $file ls -alhg $file for i in $(seq 1 $retries) ; do time sed $pos'q;d' $file done /bin/rm $file 

Čia pateikiami mano įrenginio veikimo rezultatai („ThinkPad X1 Carbon“ su SSD ir 16 GB atminties). Manau, kad galiausiai viskas bus iš talpyklos, o ne iš disko:

 *** head -N | tail -1 *** 500000000 real 0m9,800s user 0m7,328s sys 0m4,081s 500000000 real 0m4,231s user 0m5,415s sys 0m2,789s 500000000 real 0m4,636s user 0m5,935s sys 0m2,684s ------------------------- *** tail -n+N | head -1 *** -rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt 500000000 real 0m6,452s user 0m3,367s sys 0m1,498s 500000000 real 0m3,890s user 0m2,921s sys 0m0,952s 500000000 real 0m3,763s user 0m3,004s sys 0m0,760s ------------------------- *** sed Nq;d *** -rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt 500000000 real 0m23,675s user 0m21,557s sys 0m1,523s 500000000 real 0m20,328s user 0m18,971s sys 0m1,308s 500000000 real 0m19,835s user 0m18,830s sys 0m1,004s 
9
31 июля '17 в 16:10 2017-07-31 16:10 atsakymą pateikė Filipas Claßenas liepos 17 d. 17 val. 16:10 2017-07-31 16:10

Taip pat galite naudoti „Perl“:

 perl -wnl -e '$.== NUM  print  exit;' some.file 
7
16 мая '11 в 22:43 2011-05-16 22:43 Atsakymą pateikė Timofey Stolbov gegužės 16 d., 11 val. 10.43 val. 2011-05-16 22:43

Greičiausias sprendimas dideliems failams visada turi uodegą, jei yra du atstumai:

  • nuo failo pradžios iki pradžios linijos. Leiskite jam paskambinti S
  • atstumas nuo paskutinės eilutės iki failo pabaigos. Būkite E

yra žinomi. Tada galėtume tai naudoti:

 mycount="$E"; (( E > S ))  mycount="+$S" howmany="$(( endline - startline + 1 ))" tail -n "$mycount"| head -n "$howmany" 

kiek yra tik reikalingų linijų skaičius.

Daugiau informacijos rasite https://unix.stackexchange.com/a/216614/79743

6
17 июля '15 в 8:34 2015-07-17 08:34 atsakymą pateikė vartotojo2350426 liepos 17 d. 15 val. 8:34 2015-07-17 08:34

Visi pirmiau pateikti atsakymai tiesiogiai atsako į klausimą. Tačiau čia yra mažiau tiesioginis sprendimas, bet galbūt svarbesnė idėja išprovokuoti mintį.

Kadangi linijų ilgiai yra savavališki, turi būti perskaityti visi failo baitai prieš n. Eilutę. Jei turite didžiulį failą arba jums reikia pakartoti šią užduotį daug kartų, ir šis procesas užtrunka ilgai, turėtumėte rimtai apsvarstyti, ar pirmiausia turite saugoti savo duomenis kitaip.

Tikrasis sprendimas yra, pavyzdžiui, indeksas. failo pradžioje, nurodant pozicijas, kuriose prasideda linijos. Galite naudoti duomenų bazės formatą arba tiesiog pridėti lentelę failo pradžioje. Arba galite sukurti atskirą indekso failą, kuris pridedamas prie jūsų didelio teksto failo.

Pavyzdžiui. Galite sukurti simbolių pozicijų sąrašą naujoms eilutėms:

 awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx 

tada perskaitykite su tail , kuri iš tikrųjų tiesiogiai seek į atitinkamą failo tašką!

Pavyzdžiui. gauti 1000 eilutę:

 tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1 
  • Tai gali neveikti su 2 baitų / kelių baitų simboliais, nes awk yra simbolinis, bet uodega nėra.
  • Aš ne išbandžiau šį didelį failą.
  • Taip pat žr. Šį atsakymą .
  • Arba galite padalinti failą į mažesnius failus.
4
12 окт. atsakymas, kurį pateikė Sanjay Manohar 12 spalis 2017-10-12 13:44 '17, 13:44 pm 2017-10-12 13:44

Todėl „CaffeineConnoisseur“ turi labai naudingą palyginimo atsakymą ... Man buvo įdomu, kaip greitai palygintas „mapfile“ metodas su kitais (nes jis nebuvo patikrintas), todėl bandžiau greitai ir nešvariai palyginti greitį, kaip aš turiu 4. Jis atsisakė „uodegos“ metodo (vietoj galvos) bandymo, paminėto viename iš pagrindinių atsakymų komentarų, kai buvau ant jo, kai žmonės dainuoja jų giria. Turiu beveik jokio testo failo dydžio; Geriausias, kurį galėčiau rasti per trumpiausią įmanomą laiką, yra 14M kilmės failas (ilgosios eilutės, atskirtos tarpais, iš viso 12 000 eilučių).

Trumpas variantas: mapfile pasirodo greičiau nei supjaustytas metodas, bet lėčiau nei bet kas kitas, todėl aš jį vadinčiau. uodega | galvos, OTOH, atrodo, kad ji gali būti greičiausia, nors su tokio dydžio failu skirtumas nėra toks reikšmingas, palyginti su sed.

 $ time head -11000 [filename] | tail -1 [output redacted] real 0m0.117s $ time cut -f11000 -d$'\n' [filename] [output redacted] real 0m1.081s $ time awk 'NR == 11000 {print; exit}' [filename] [output redacted] real 0m0.058s $ time perl -wnl -e '$.== 11000  print  exit;' [filename] [output redacted] real 0m0.085s $ time sed "11000q;d" [filename] [output redacted] real 0m0.031s $ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]}) [output redacted] real 0m0.309s $ time tail -n+11000 [filename] | head -n1 [output redacted] real 0m0.028s 

Tikiuosi, kad tai padės!

4
10 янв. Jo Valentine-Cooper atsakymas sausio 10 d 2018-01-10 17:11 '18, 17:11 pm 2018-01-10 17:11

Jei gausite kelias eilutes, jas atskirite n (paprastai nauja eilutė). Taip pat galite naudoti „iškirpti“:

 echo "$data" | cut -f2 -d$'\n' 

Iš failo gausite antrą eilutę. -f3 suteikia jums trečią eilutę.

3
07 янв. atsakymas pateikiamas pavojus89 Sau 07 2016-01-07 19:27 '16 at 7:27 pm 2016-01-07 19:27

Vienas iš galimų būdų:

 sed -n 'NUM{p;q}' 

Atkreipkite dėmesį, kad be q komandos, jei failas yra didelis, sed ir toliau veikia, o tai lėtina skaičiavimą.

2
16 марта '16 в 17:01 2016-03-16 17:01 atsakymą pateikė Jindra Helcl kovo 16–16 d. 17:01 2016-03-16 17:01

Jau yra daug gerų atsakymų. Aš asmeniškai persijungiu iš awk. Jei norite naudoti patogumą, jei naudojate „bash“, tiesiog pridėkite žemiau savo ~/.bash_profile failą. Kai kitą kartą prisijungsite (arba jei po šio naujinimo išsiųsite .bash_profile), turėsite puikią „n-ojo“ funkciją, skirtą failams perkelti.

Padarykite tai įdėkite į savo ~ / .bash_profile failą (jei naudojate bash) ir paleiskite bash dar kartą (arba source ~/.bach_profile )

# print just the nth piped in line nth() { awk -vlnum=${1} 'NR==lnum {print; exit}'; }

Tada, norėdami ją naudoti, tiesiog braukite per jį. Pavyzdžiui:

$ yes line | cat -n | nth 5 5 line

2
17 нояб. JJC atsakė lapkričio 17 d 2017-11-17 18:42 '17, 18:42 pm 2017-11-17 18:42

Naudodamas tai, ką kiti sakė, norėjau, kad tai būtų greita ir dandy funkcija mano „bash“ liemenėje.

Sukurti failą: ~/.functions

Pridėti turinį:

getline() { line=$1 sed $line'q;d' $2 }

Tada pridėkite ją prie savo ~/.bash_profile failo:

source ~/.functions

Dabar, kai atidarote naują „bash“ >

getline 441 myfile.txt

1
17 янв. Atsakymas, kurį pateikė Mark Shust Jan 17 2018-01-17 17:19 '18, 18:19 PM 2018-01-17 17:19

Jei norite spausdinti n-ą eilutę su sed su kintamuoju kaip eilutės numeris:

 a=4 sed -e $a'q:d' file 

Čia vėliavėlė „-e“ skirta pridėti scenarijų komandai vykdyti.

1
13 марта '15 в 9:40 2015-03-13 09:40 atsakymas pateikiamas aliasav kovo 13 d., 15 val. 9:40 2015-03-13 09:40

Kai kuriuos iš pirmiau pateiktų atsakymų įdėjau į trumpą „bash“ scenarijų, kurį galite įdėti į failą, pavadintą get.sh ir susieti jį su /usr/local/bin/get (arba bet kuriuo kitu vardu).

 #!/bin/bash if [ "${1}" == "" ]; then echo "error: blank line number"; exit 1 fi re='^[0-9]+$' if ! [[ $1 =~ $re ]] ; then echo "error: line number arg not a number"; exit 1 fi if [ "${2}" == "" ]; then echo "error: blank file name"; exit 1 fi sed "${1}q;d" $2; exit 0 

Įsitikinkite, kad jis vykdomas

 $ chmod +x get 

Susieti jį, kad jis būtų prieinamas PATH su

 $ ln -s get.sh /usr/local/bin/get 

Mėgaukitės atsakingai!

n

0
28 янв. atsakymas pateikiamas polarizuoti sausio 28 d 2019-01-28 18:22 '19, 18:22 PM 2019-01-28 18:22

Kiti klausimai apie žymes arba Užduoti klausimą