Narzędzie Bash, aby uzyskać n-tą linię z pliku

603

Czy istnieje „kanoniczny” sposób na zrobienie tego? Używam tego, head -n | tail -1co robi, ale zastanawiam się, czy istnieje narzędzie Bash, które konkretnie wyodrębnia linię (lub zakres linii) z pliku.

Przez „kanoniczny” rozumiem program, którego główna funkcja to robi.

Vlad Vivdovitch
źródło
10
„Uniksowym sposobem” jest łączenie narzędzi, które dobrze wykonują swoje zadania. Myślę więc, że już znalazłeś bardzo odpowiednią metodę. Inne metody obejmują awki sedjestem pewien, że ktoś może wymyślić również linijkę Perla;)
0xC0000022L
3
Podwójne polecenie sugeruje, że head | tailrozwiązanie nie jest optymalne. Sugerowano inne, prawie optymalne rozwiązania.
Jonathan Leffler
Czy przeprowadziłeś już jakieś testy porównawcze, dla których rozwiązanie jest najszybsze w przeciętnym przypadku?
Marcin
5
Testy porównawcze (dla zakresu) na linii X od linii Y do ogromnego pliku w systemach Unix i Linux . (cc @Marcin, na wypadek, gdy nadal zastanawiasz się po ponad dwóch latach)
Kevin
6
head | tailRozwiązanie nie działa, jeśli zapytanie linię, która nie istnieje na wejściu: będzie wydrukować ostatnią linię.
jarno

Odpowiedzi:

800

headi potok z tailbędzie wolny dla dużego pliku. Sugerowałbym sedtak:

sed 'NUMq;d' file

Gdzie NUMjest numer linii, którą chcesz wydrukować; na przykładsed '10q;d' file aby wydrukować 10. linię file.

Wyjaśnienie:

NUMqnatychmiast wyjdzie, gdy numer linii to NUM.

dusunie wiersz zamiast go wydrukować; jest to zablokowane w ostatnim wierszu, ponieważ qpowoduje pominięcie reszty skryptu podczas zamykania.

Jeśli masz NUMzmienną, będziesz chciał użyć podwójnych cudzysłowów zamiast pojedynczych:

sed "${NUM}q;d" file
anubhava
źródło
44
Dla tych, którzy zastanawiają się, to rozwiązanie wydaje się około 6 do 9 razy szybsze niż sed -n 'NUMp'i sed 'NUM!d'rozwiązania zaproponowane poniżej.
Skippy le Grand Gourou
75
Myślę, że tail -n+NUM file | head -n1prawdopodobnie będzie tak samo szybko lub szybciej. Przynajmniej był (znacznie) szybszy w moim systemie, kiedy wypróbowałem go, mając NUM 250000 na pliku z pół milionem linii. YMMV, ale tak naprawdę nie rozumiem dlaczego.
rici
2
@rici (wersja wcześniejszego komentarza) W systemie Linux (Ubuntu 12.04, Fedora 20) korzystanie catjest rzeczywiście szybsze (prawie dwa razy szybsze), ale tylko wtedy, gdy plik nie został jeszcze buforowany . Po buforowaniu pliku bezpośrednie użycie argumentu nazwa pliku jest szybsze (około 1/3 szybciej), a catwydajność pozostaje taka sama. Co ciekawe, w OS X 10.9.3 wydaje się, że nic z tego nie robi różnicy: cat/ nie cat, plik jest buforowany lub nie. @anubhava: moja przyjemność.
mklement0
2
@SkippyleGrandGourou: Biorąc pod uwagę szczególny charakter tej optymalizacji , nawet zakresy liczb są bezcelowe jako ogólne stwierdzenie . Jedyne ogólne podejście na wynos jest następujące: (a) optymalizację tę można bezpiecznie zastosować do wszystkich danych wejściowych, (b) efekty będą wahać się od zerowego do dramatycznego , w zależności od indeksu poszukiwanej linii w stosunku do liczby ogólnych linii.
mklement0
17
sed 'NUMqwypisze pierwsze NUMpliki i ;dusunie wszystkie oprócz ostatniego wiersza.
anubhava,
304
sed -n '2p' < file.txt

wydrukuje 2. linię

sed -n '2011p' < file.txt

2011. linia

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

linia 10 do linii 33

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

1. i 3. linia

i tak dalej...

Aby dodać linie za pomocą sed, możesz to sprawdzić:

sed: wstaw linię w określonej pozycji

jm666
źródło
6
@RafaelBarbosa <w tym przypadku nie jest konieczne. Po prostu preferuję przekierowania, ponieważ często używałem przekierowań typu sed -n '100p' < <(some_command)- tak, uniwersalna składnia :). NIE jest to mniej skuteczne, ponieważ przekierowywanie odbywa się za pomocą powłoki przy rozwidlaniu się, więc ... jest to tylko preferencja ... (i tak, jest to jedna postać dłużej) :)
jm666
1
@ jm666 Właściwie to są 2 znaki dłużej, ponieważ normalnie wstawiłbyś „<” oraz dodatkową spację po <w przeciwieństwie do tylko jednej spacji, jeśli nie użyłeś <:)
rasen58
2
@ rasen58 spacja też jest postacią? :) / ok, tylko żartuję - masz rację / :)
jm666
1
@duhaime oczywiście, jeśli ktoś potrzebuje optymalizacji. Ale IMHO dla „typowych” problemów jest w porządku, a różnica jest niezauważalna. Również head/ tailnie rozwiązuje sed -n '1p;3p'scenariusza - czyli drukuje więcej nieprzylegających wierszy ...
jm666
1
@duhaime oczywiście - notatka jest poprawna i potrzebna. :)
jm666
93

Mam wyjątkową sytuację, w której mogę przeprowadzić analizę porównawczą rozwiązań zaproponowanych na tej stronie, dlatego piszę tę odpowiedź jako konsolidację proponowanych rozwiązań z uwzględnieniem każdego z nich.

Ustawiać

Mam plik danych tekstowych ASCII o rozmiarze 3,261 gigabajta z jedną parą klucz-wartość na wiersz. Plik zawiera łącznie 3333950320 wierszy i odmawia otwarcia w dowolnym edytorze, którego próbowałem, w tym w moim Vimie. Muszę podzestawić ten plik, aby zbadać niektóre wartości, które odkryłem, zaczynają się tylko wokół rzędu ~ 500 000 000.

Ponieważ plik ma tak wiele wierszy:

  • Muszę wyodrębnić tylko podzbiór wierszy, aby zrobić coś użytecznego z danymi.
  • Przeczytanie każdego wiersza prowadzącego do wartości, na których mi zależy, zajmie dużo czasu.
  • Jeśli rozwiązanie odczyta wiersze, na których mi zależy, i będzie kontynuowało czytanie pozostałej części pliku, straci czas na odczytanie prawie 3 miliardów nieistotnych wierszy i zajmie 6 razy dłużej niż to konieczne.

Mój najlepszy scenariusz to rozwiązanie, które wyodrębnia tylko jeden wiersz z pliku bez odczytywania innych wierszy w pliku, ale nie mogę wymyślić, jak to osiągnę w Bash.

Dla mojego zdrowia psychicznego nie zamierzam czytać pełnych 500 000 000 wierszy, których potrzebowałbym na swój problem. Zamiast tego spróbuję wyodrębnić wiersz 50 000 000 z 3333950320 (co oznacza, że ​​odczyt całego pliku zajmie 60x dłużej niż to konieczne).

Będę używał timewbudowanego do testowania każdego polecenia.

Linia bazowa

Najpierw zobaczmy, jak head tailrozwiązanie:

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

Linia bazowa dla wiersza 50 milionów to 00: 01: 15.321, gdybym poszedł prosto do wiersza 500 milionów, byłoby to prawdopodobnie około 12,5 minuty.

skaleczenie

Wątpię w to, ale warto spróbować:

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

Uruchomienie tego zajęło 00: 05: 12.156, co jest znacznie wolniejsze niż poziom podstawowy! Nie jestem pewien, czy przed zatrzymaniem przeczytał cały plik, czy tylko 50 milionów, ale niezależnie od tego nie wydaje się to realnym rozwiązaniem problemu.

AWK

Uruchomiłem rozwiązanie tylko exitdlatego, że nie zamierzałem czekać na uruchomienie pełnego pliku:

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

Ten kod działał w 00: 01: 16.583, co jest tylko ~ 1 sekundę wolniejsze, ale wciąż nie stanowi poprawy w stosunku do linii podstawowej. Przy takim tempie, gdyby polecenie zakończenia zostało wykluczone, odczytanie całego pliku zajęłoby prawdopodobnie około 76 minut!

Perl

Uruchomiłem również istniejące rozwiązanie Perla:

$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

Ten kod działał w 00: 01: 13.146, czyli około 2 sekundy szybciej niż poziom podstawowy. Gdybym uruchomił go na pełnych 500 000 000, prawdopodobnie zajęłoby to około 12 minut.

sed

Najlepsza odpowiedź na tablicy, oto mój wynik:

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

Ten kod działał w 00: 01: 12.705, czyli 3 sekundy szybciej niż poziom bazowy i ~ 0,4 sekundy szybciej niż Perl. Gdybym uruchomił go na pełnych 500 000 000 wierszach, prawdopodobnie zajęłoby to około 12 minut.

plik map

Mam bash 3.1 i dlatego nie mogę przetestować rozwiązania mapfile.

Wniosek

Wygląda na to, że w większości trudno jest poprawić to head tailrozwiązanie. W najlepszym raziesed rozwiązanie zapewnia ~ 3% wzrost wydajności.

(procenty obliczone ze wzoru % = (runtime/baseline - 1) * 100 )

Rząd 50 000 000

  1. 00: 01: 12.705 (-00: 00: 02.616 = -3,47%) sed
  2. 00: 01: 13,146 (-00: 00: 02.175 = -2,89%) perl
  3. 00: 01: 15.321 (+00: 00: 00.000 = + 0,00%) head|tail
  4. 00: 01: 16,583 (+00: 00: 01.262 = + 1,68%) awk
  5. 00: 05: 12,156 (+00: 03: 56,835 = + 314,43%) cut

Wiersz 500 000 000

  1. 00: 12: 07.050 (-00: 00: 26.160) sed
  2. 00: 12: 11.460 (-00: 00: 21.750) perl
  3. 00: 12: 33.210 (+00: 00: 00.000) head|tail
  4. 00: 12: 45,830 (+00: 00: 12,620) awk
  5. 00: 52: 01.560 (+00: 40: 31.650) cut

Wiersz 3 338,559,320

  1. 01: 20: 54.599 (-00: 03: 05.327) sed
  2. 01: 21: 24.045 (-00: 02: 25.227) perl
  3. 01: 23: 49.273 (+00: 00: 00.000) head|tail
  4. 01: 25: 13.548 (+00: 02: 35,735) awk
  5. 05: 47: 23.026 (+04: 24: 26.246) cut
Kofeina Koneser
źródło
4
Zastanawiam się, jak długo zajęłoby umieszczenie całego pliku w katalogu / dev / null. (Co jeśli
byłby
Czuję przewrotną potrzebę pochylenia się nad posiadaniem słownika plików tekstowych z ponad 3 koncertami. Bez względu na uzasadnienie, obejmuje to tekstualność :)
Stabledog
51

Dzięki awktemu jest dość szybki:

awk 'NR == num_line' file

Kiedy to prawda, domyślne zachowanie awkjest wykonywana: {print $0}.


Alternatywne wersje

Jeśli twój plik okazuje się być ogromny, lepiej exitpo przeczytaniu wymaganej linii. W ten sposób oszczędzasz czas procesora Zobacz porównanie czasu na końcu odpowiedzi .

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

Jeśli chcesz podać numer linii ze zmiennej bash, możesz użyć:

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent

Zobacz, ile czasu zaoszczędzono, używając exit, szczególnie jeśli wiersz znajduje się w pierwszej części pliku:

# Let's create a 10M lines file
for ((i=0; i<100000; i++)); do echo "bla bla"; done > 100Klines
for ((i=0; i<100; i++)); do cat 100Klines; done > 10Mlines

$ time awk 'NR == 1234567 {print}' 10Mlines
bla bla

real    0m1.303s
user    0m1.246s
sys 0m0.042s
$ time awk 'NR == 1234567 {print; exit}' 10Mlines
bla bla

real    0m0.198s
user    0m0.178s
sys 0m0.013s

Różnica wynosi 0,198s wobec 1,303s, około 6 razy szybciej.

fedorqui „SO przestań szkodzić”
źródło
Ta metoda zawsze będzie wolniejsza, ponieważ awk próbuje dokonać podziału pola. Narzut podział pola może być zmniejszona oawk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
kvantour
Prawdziwa siła awk w tej metodzie wychodzi kiedy chcesz złączyć linii N1, N2 plik1 plik2, N3 z lub file3 ... awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3. Z GNU awk można to przyspieszyć za pomocą awk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3.
kvantour
@kvantour rzeczywiście, następny plik GNU awk jest świetny do takich rzeczy. Jak to możliwe, że FS=RSunika się podziału pola?
fedorqui „SO przestań krzywdzić”
1
FS=RSnie unika dzielenia pola, ale to tylko analizuje $ 0 i tylko te przypisuje jedno pole, bo tam nie ma RSw$0
kvantour
@kvantour Robiłem testy FS=RSi nie widziałem różnicy w taktowaniu. Co powiesz na pytanie o to, abyś mógł się rozwinąć? Dzięki!
fedorqui „SO przestań szkodzić”
29

Według moich testów pod względem wydajności i czytelności zalecam:

tail -n+N | head -1

Nto żądany numer linii. Na przykład,tail -n+7 input.txt | head -1 wydrukuje siódmą linię pliku.

tail -n+Nwypisze wszystko, zaczynając od linii Ni head -1sprawi, że zatrzyma się po jednej linii.


Alternatywa head -N | tail -1jest chyba nieco bardziej czytelna. Na przykład spowoduje to wydrukowanie siódmej linii:

head -7 input.txt | tail -1

Jeśli chodzi o wydajność, nie ma dużej różnicy w przypadku mniejszych rozmiarów, ale będzie lepszy niż tail | head (z góry), gdy pliki staną się ogromne.

Najwyżej głosowani sed 'NUMq;d' jest jest interesujący, ale twierdzę, że mniej zrozumiałych będzie dla niego mniej osób niż rozwiązanie głowa / ogon, a także wolniejsze niż ogon / głowa.

W moich testach obie wersje ogonów / głów wypadły lepiej sed 'NUMq;d' . Jest to zgodne z innymi opublikowanymi testami porównawczymi. Trudno znaleźć przypadek, w którym ogony / głowy były naprawdę złe. Nie jest to również zaskakujące, ponieważ są to operacje, które można by mocno zoptymalizować w nowoczesnym systemie uniksowym.

Aby dowiedzieć się o różnicach w wydajności, oto liczba, którą otrzymuję dla dużego pliku (9.3G):

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

Wyniki mogą się różnić, ale wydajność head | taili tail | head, ogólnie rzecz biorąc, jest porównywalna dla mniejszych nakładów i sedjest zawsze wolniejsza o znaczący czynnik (około 5x lub więcej).

Aby odtworzyć mój test porównawczy, możesz wypróbować następujące czynności, ale pamiętaj, że utworzy on plik 9.3G w bieżącym katalogu roboczym:

#!/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

Oto wyjście z pracy na moim komputerze (ThinkPad X1 Carbon z dyskiem SSD i 16G pamięci). Zakładam, że w ostatnim uruchomieniu wszystko będzie pochodziło z pamięci podręcznej, a nie z dysku:

*** 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
Philipp Claßen
źródło
1
Czy wydajność różni się między head | tailvs tail | head? A może zależy to od tego, która linia jest drukowana (początek pliku czy koniec pliku)?
wisbucky
1
@wisbucky Nie mam twardych liczb, ale jedną wadą pierwszego użycia ogona, a następnie „głowy -1” jest to, że musisz znać całkowitą długość z góry. Jeśli tego nie wiesz, najpierw musisz to policzyć, co będzie stratą wydajności. Kolejną wadą jest to, że jest mniej intuicyjny w użyciu. Na przykład, jeśli masz liczbę od 1 do 10 i chcesz uzyskać trzecią linię, musisz użyć „tail -8 | head -1”. Jest to bardziej podatne na błędy niż „głowa -3 | ogon -1”.
Philipp Claßen
przepraszam, powinienem podać przykład, aby był jasny. head -5 | tail -1vs tail -n+5 | head -1. Właściwie znalazłem inną odpowiedź, która zrobiła porównanie testowe i okazała tail | headsię szybsza. stackoverflow.com/a/48189289
wisbucky
1
@wisbucky Dziękujemy za wzmiankę o tym! Zrobiłem kilka testów i muszę się zgodzić, że zawsze było nieco szybciej, niezależnie od pozycji linii od tego, co widziałem. Biorąc to pod uwagę, zmieniłem swoją odpowiedź i uwzględniłem test porównawczy na wypadek, gdyby ktoś chciał go odtworzyć.
Philipp Claßen,
27

Wow, wszystkie możliwości!

Spróbuj tego:

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

lub jeden z nich w zależności od wersji Awk:

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

( Być może będziesz musiał spróbować użyć polecenia nawklubgawk ).

Czy istnieje narzędzie, które drukuje tylko ten konkretny wiersz? Żadne ze standardowych narzędzi. Jest jednak sedprawdopodobnie najbliższy i najprostszy w użyciu.

David W.
źródło
21

To pytanie jest oznaczone jako Bash, oto sposób wykonywania Bash (≥4): użyj mapfilez opcją -s(pomiń) i -n(policz).

Jeśli potrzebujesz uzyskać 42 linię pliku file:

mapfile -s 41 -n 1 ary < file

W tym momencie będziesz mieć tablicę, aryktórej pola zawierają linie file(w tym nowej linii), w której pominęliśmy pierwsze 41 linii ( -s 41) i zatrzymaliśmy się po przeczytaniu jednej linii ( -n 1). To naprawdę 42 linia. Aby wydrukować:

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

Jeśli potrzebujesz zakresu linii, powiedz zakres 42–666 (włącznie) i powiedz, że nie chcesz samodzielnie wykonywać obliczeń matematycznych, i wydrukuj je na standardowym ekranie:

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

Jeśli potrzebujesz również przetworzyć te linie, nie jest wygodne przechowywanie końcowego znaku nowej linii. W takim przypadku skorzystaj z -topcji (przycinanie):

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

Możesz mieć funkcję, która zrobi to za Ciebie:

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[@]}"
}

Brak zewnętrznych poleceń, tylko wbudowane Bash!

gniourf_gniourf
źródło
11

Możesz także użyć sed print i wyjść:

sed -n '10{p;q;}' file   # print line 10
bernd
źródło
6
Ta -nopcja wyłącza domyślną akcję drukowania każdej linii, ponieważ na pewno dowiedziałbyś się tego poprzez szybkie spojrzenie na stronę podręcznika.
tripleee
W GNU sed wszystkie sedodpowiedzi są mniej więcej takie same. Dlatego (dla GNU sed ) jest to najlepsza sedodpowiedź, ponieważ pozwoliłoby zaoszczędzić czas dla dużych plików i małych wartości n-tej linii .
agc
7

Możesz również użyć Perla do tego:

perl -wnl -e '$.== NUM && print && exit;' some.file
Timofey Stolbov
źródło
6

Najszybszym rozwiązaniem dla dużych plików jest zawsze tail | head, pod warunkiem, że dwie odległości:

  • od początku pliku do linii początkowej. Nazwijmy toS
  • odległość od ostatniej linii do końca pliku. Niech tak będzieE

są znane. Następnie moglibyśmy użyć tego:

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

ile to tylko liczba wymaganych wierszy.

Więcej szczegółów w https://unix.stackexchange.com/a/216614/79743

Społeczność
źródło
1
Proszę wyjaśnić jednostki Si E(tj. Bajty, znaki lub wiersze).
agc
6

Wszystkie powyższe odpowiedzi bezpośrednio odpowiadają na pytanie. Ale oto mniej bezpośrednie rozwiązanie, ale potencjalnie ważniejszy pomysł, aby sprowokować myśl.

Od długości przewodów są arbitralne, wszystkie bajty pliku przed n-tego wiersza potrzebie zostać odczytane. Jeśli masz ogromny plik lub musisz powtórzyć to zadanie wiele razy, a ten proces jest czasochłonny, powinieneś poważnie pomyśleć o tym, czy powinieneś przechowywać dane w inny sposób.

Prawdziwym rozwiązaniem jest indeks, np. Na początku pliku, wskazujący pozycje, od których zaczynają się linie. Możesz użyć formatu bazy danych lub po prostu dodać tabelę na początku pliku. Możesz też utworzyć osobny plik indeksu, który będzie towarzyszył dużemu plikowi tekstowemu.

np. możesz utworzyć listę pozycji znaków dla nowych linii:

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

następnie czytaj za pomocą tail, który faktycznie znajduje się seekbezpośrednio w odpowiednim punkcie pliku!

np. aby uzyskać linię 1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • Może to nie działać ze znakami 2-bajtowymi / wielobajtowymi, ponieważ awk jest „świadomy znaków”, ale ogon nie.
  • Nie testowałem tego na dużym pliku.
  • Zobacz także tę odpowiedź .
  • Alternatywnie - podziel plik na mniejsze pliki!
Sanjay Manohar
źródło
5

Jako kontynuacja bardzo pomocnej odpowiedzi testu porównawczego CaffeineConnoisseur ... Byłem ciekawy, jak szybko metoda „mapfile” jest porównywana z innymi (ponieważ nie było to testowane), więc sam spróbowałem szybkiego i brudnego porównania prędkości, ponieważ Mam pod ręką bash 4. Wrzuciłem test metody „ogon | głowa” (zamiast głowy | ogona) wspomnianej w jednym z komentarzy do najwyższej odpowiedzi, gdy ja tam byłem, ponieważ ludzie śpiewają jej pochwały. Nie mam nic prawie wielkości użytego pliku testowego; najlepszym, co udało mi się znaleźć w krótkim czasie, był 14-metrowy plik rodowodu (długie linie, które są oddzielone białymi spacjami, nieco poniżej 12000 linii).

Krótka wersja: plik map pojawia się szybciej niż metoda cięcia, ale wolniej niż wszystko inne, więc nazwałbym to niewypałem. ogon | head, OTOH, wygląda na to, że może być najszybszy, chociaż przy takim rozmiarze pliku różnica nie jest aż tak duża w porównaniu do 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

Mam nadzieję że to pomoże!

Jo Valentine-Cooper
źródło
4

Korzystając z tego, co wspomnieli inni, chciałem, aby była to szybka i elegancka funkcja w mojej powłoce bash.

Utwórz plik: ~/.functions

Dodaj do niego zawartość:

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

Następnie dodaj to do ~/.bash_profile:

source ~/.functions

Teraz, gdy otworzysz nowe okno bash, możesz po prostu wywołać tę funkcję w następujący sposób:

getline 441 myfile.txt

Mark Shust w M.academy
źródło
3

Jeśli masz wiele linii rozdzielanych przez \ n (zwykle nowa linia). Możesz także użyć opcji „Wytnij”:

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

Otrzymasz drugą linię z pliku. -f3daje trzecią linię.

niebezpieczeństwo 89
źródło
1
Może być również używany do wyświetlania wielu linii: cat FILE | cut -f2,5 -d$'\n'wyświetla linie 2 i 5 PLIKU. (Ale to nie zachowa porządku.)
Andriy Makukha
2

Aby wydrukować n-tą linię przy użyciu sed ze zmienną jako numerem linii:

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

Tutaj flaga „-e” służy do dodawania skryptu do polecenia, które ma zostać wykonane.

aliasav
źródło
2
Dwukropek jest błędem składniowym i powinien być średnikiem.
tripleee
2

Wiele dobrych odpowiedzi już. Ja osobiście idę z awk. Dla wygody, jeśli używasz bash, po prostu dodaj poniżej swój ~/.bash_profile. I przy następnym logowaniu (lub jeśli po aktualizacji zaktualizujesz plik .bash_profile), będziesz mieć nową, sprytną funkcję „n-tego”, umożliwiającą przesyłanie plików.

Wykonaj to lub umieść w swoim ~ / .bash_profile (jeśli używasz bash) i ponownie otwórz bash (lub uruchom source ~/.bach_profile)

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

Następnie, aby go użyć, po prostu przesuń go. Na przykład,:

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

JJC
źródło
1

Po przyjrzeniu górnym odpowiedź i po teście , I zostały wdrożone maleńką funkcji pomocnika:

function nth {
    if (( ${#} < 1 || ${#} > 2 )); then
        echo -e "usage: $0 \e[4mline\e[0m [\e[4mfile\e[0m]"
        return 1
    fi
    if (( ${#} > 1 )); then
        sed "$1q;d" $2
    else
        sed "$1q;d"
    fi
}

Zasadniczo można go używać w dwóch modach:

nth 42 myfile.txt
do_stuff | nth 42
Ulysse BN
źródło
0

Niektóre z powyższych odpowiedzi umieściłem w krótkim skrypcie bash, który można umieścić w pliku o nazwie get.shi linku do /usr/local/bin/get(lub dowolnej innej nazwy, którą preferujesz).

#!/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

Upewnij się, że jest wykonywalny za pomocą

$ chmod +x get

Połącz go, aby był dostępny za PATHpomocą

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

Ciesz się odpowiedzialnie!

P.

polaryzować
źródło