Czy istnieje „kanoniczny” sposób na zrobienie tego? Używam tego, head -n | tail -1
co 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.
awk
ised
jestem pewien, że ktoś może wymyślić również linijkę Perla;)head | tail
rozwiązanie nie jest optymalne. Sugerowano inne, prawie optymalne rozwiązania.head | tail
Rozwiązanie nie działa, jeśli zapytanie linię, która nie istnieje na wejściu: będzie wydrukować ostatnią linię.Odpowiedzi:
head
i potok ztail
będzie wolny dla dużego pliku. Sugerowałbymsed
tak:Gdzie
NUM
jest numer linii, którą chcesz wydrukować; na przykładsed '10q;d' file
aby wydrukować 10. linięfile
.Wyjaśnienie:
NUMq
natychmiast wyjdzie, gdy numer linii toNUM
.d
usunie wiersz zamiast go wydrukować; jest to zablokowane w ostatnim wierszu, ponieważq
powoduje pominięcie reszty skryptu podczas zamykania.Jeśli masz
NUM
zmienną, będziesz chciał użyć podwójnych cudzysłowów zamiast pojedynczych:źródło
sed -n 'NUMp'
ised 'NUM!d'
rozwiązania zaproponowane poniżej.tail -n+NUM file | head -n1
prawdopodobnie 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.cat
jest 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), acat
wydajność pozostaje taka sama. Co ciekawe, w OS X 10.9.3 wydaje się, że nic z tego nie robi różnicy:cat
/ niecat
, plik jest buforowany lub nie. @anubhava: moja przyjemność.sed 'NUMq
wypisze pierwszeNUM
pliki i;d
usunie wszystkie oprócz ostatniego wiersza.wydrukuje 2. linię
2011. linia
linia 10 do linii 33
1. i 3. linia
i tak dalej...
Aby dodać linie za pomocą sed, możesz to sprawdzić:
sed: wstaw linię w określonej pozycji
źródło
<
w tym przypadku nie jest konieczne. Po prostu preferuję przekierowania, ponieważ często używałem przekierowań typused -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) :)head
/tail
nie rozwiązujesed -n '1p;3p'
scenariusza - czyli drukuje więcej nieprzylegających wierszy ...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:
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ł
time
wbudowanego do testowania każdego polecenia.Linia bazowa
Najpierw zobaczmy, jak
head
tail
rozwiązanie: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ć:
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
exit
dlatego, że nie zamierzałem czekać na uruchomienie pełnego pliku: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:
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:
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
tail
rozwiązanie. W najlepszym raziesed
rozwiązanie zapewnia ~ 3% wzrost wydajności.(procenty obliczone ze wzoru
% = (runtime/baseline - 1) * 100
)Rząd 50 000 000
sed
perl
head|tail
awk
cut
Wiersz 500 000 000
sed
perl
head|tail
awk
cut
Wiersz 3 338,559,320
sed
perl
head|tail
awk
cut
źródło
Dzięki
awk
temu jest dość szybki:Kiedy to prawda, domyślne zachowanie
awk
jest wykonywana:{print $0}
.Alternatywne wersje
Jeśli twój plik okazuje się być ogromny, lepiej
exit
po przeczytaniu wymaganej linii. W ten sposób oszczędzasz czas procesora Zobacz porównanie czasu na końcu odpowiedzi .Jeśli chcesz podać numer linii ze zmiennej bash, możesz użyć:
Zobacz, ile czasu zaoszczędzono, używając
exit
, szczególnie jeśli wiersz znajduje się w pierwszej części pliku:Różnica wynosi 0,198s wobec 1,303s, około 6 razy szybciej.
źródło
awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
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
.FS=RS
unika się podziału pola?FS=RS
nie unika dzielenia pola, ale to tylko analizuje $ 0 i tylko te przypisuje jedno pole, bo tam nie maRS
w$0
FS=RS
i nie widziałem różnicy w taktowaniu. Co powiesz na pytanie o to, abyś mógł się rozwinąć? Dzięki!Według moich testów pod względem wydajności i czytelności zalecam:
tail -n+N | head -1
N
to żądany numer linii. Na przykład,tail -n+7 input.txt | head -1
wydrukuje siódmą linię pliku.tail -n+N
wypisze wszystko, zaczynając od liniiN
ihead -1
sprawi, że zatrzyma się po jednej linii.Alternatywa
head -N | tail -1
jest 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 sekhead -N | tail -1
: 4,6 ssed Nq;d
: 18,8 sekWyniki mogą się różnić, ale wydajność
head | tail
itail | head
, ogólnie rzecz biorąc, jest porównywalna dla mniejszych nakładów ised
jest 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:
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:
źródło
head | tail
vstail | head
? A może zależy to od tego, która linia jest drukowana (początek pliku czy koniec pliku)?head -5 | tail -1
vstail -n+5 | head -1
. Właściwie znalazłem inną odpowiedź, która zrobiła porównanie testowe i okazałatail | head
się szybsza. stackoverflow.com/a/48189289Wow, wszystkie możliwości!
Spróbuj tego:
lub jeden z nich w zależności od wersji Awk:
( Być może będziesz musiał spróbować użyć polecenia
nawk
lubgawk
).Czy istnieje narzędzie, które drukuje tylko ten konkretny wiersz? Żadne ze standardowych narzędzi. Jest jednak
sed
prawdopodobnie najbliższy i najprostszy w użyciu.źródło
Przydatne skrypty jednowierszowe dla sed
źródło
To pytanie jest oznaczone jako Bash, oto sposób wykonywania Bash (≥4): użyj
mapfile
z opcją-s
(pomiń) i-n
(policz).Jeśli potrzebujesz uzyskać 42 linię pliku
file
:W tym momencie będziesz mieć tablicę,
ary
której pola zawierają liniefile
(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ć: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:
Jeśli potrzebujesz również przetworzyć te linie, nie jest wygodne przechowywanie końcowego znaku nowej linii. W takim przypadku skorzystaj z
-t
opcji (przycinanie):Możesz mieć funkcję, która zrobi to za Ciebie:
Brak zewnętrznych poleceń, tylko wbudowane Bash!
źródło
Możesz także użyć sed print i wyjść:
źródło
-n
opcja wyłącza domyślną akcję drukowania każdej linii, ponieważ na pewno dowiedziałbyś się tego poprzez szybkie spojrzenie na stronę podręcznika.sed
wszystkiesed
odpowiedzi są mniej więcej takie same. Dlatego (dla GNUsed
) jest to najlepszased
odpowiedź, ponieważ pozwoliłoby zaoszczędzić czas dla dużych plików i małych wartości n-tej linii .Możesz również użyć Perla do tego:
źródło
Najszybszym rozwiązaniem dla dużych plików jest zawsze tail | head, pod warunkiem, że dwie odległości:
S
E
są znane. Następnie moglibyśmy użyć tego:
ile to tylko liczba wymaganych wierszy.
Więcej szczegółów w https://unix.stackexchange.com/a/216614/79743
źródło
S
iE
(tj. Bajty, znaki lub wiersze).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:
następnie czytaj za pomocą
tail
, który faktycznie znajduje sięseek
bezpośrednio w odpowiednim punkcie pliku!np. aby uzyskać linię 1000:
źródło
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.
Mam nadzieję że to pomoże!
źródło
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
źródło
Jeśli masz wiele linii rozdzielanych przez \ n (zwykle nowa linia). Możesz także użyć opcji „Wytnij”:
Otrzymasz drugą linię z pliku.
-f3
daje trzecią linię.źródło
cat FILE | cut -f2,5 -d$'\n'
wyświetla linie 2 i 5 PLIKU. (Ale to nie zachowa porządku.)Aby wydrukować n-tą linię przy użyciu sed ze zmienną jako numerem linii:
Tutaj flaga „-e” służy do dodawania skryptu do polecenia, które ma zostać wykonane.
źródło
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
źródło
Po przyjrzeniu górnym odpowiedź i po teście , I zostały wdrożone maleńką funkcji pomocnika:
Zasadniczo można go używać w dwóch modach:
źródło
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.sh
i linku do/usr/local/bin/get
(lub dowolnej innej nazwy, którą preferujesz).Upewnij się, że jest wykonywalny za pomocą
Połącz go, aby był dostępny za
PATH
pomocąCiesz się odpowiedzialnie!
P.
źródło