Jak mogę usunąć pierwszy wiersz pliku tekstowego za pomocą skryptu bash / sed?

554

Muszę wielokrotnie usuwać pierwszą linię z dużego pliku tekstowego za pomocą skryptu bash.

Obecnie używam sed -i -e "1d" $FILE- ale usunięcie zajmuje około minuty.

Czy istnieje bardziej skuteczny sposób na osiągnięcie tego?

Brent
źródło
co oznacza -i?
cikatomo
4
@cikatomo: oznacza edycję bezpośrednią - edytuje plik za pomocą tego, co wygenerujesz.
drewrockshard
4
ogon jest DUŻO WOLNY niż sed. ogon potrzebuje 13,5 sekundy, sed potrzebuje 0,85 sekundy. Mój plik ma ~ 1 mln linii, ~ 100 MB. MacBook Air 2013 z dyskiem SSD.
jcsahnwaldt mówi GoFundMonica

Odpowiedzi:

1029

Spróbuj ogona :

tail -n +2 "$FILE"

-n x: Wystarczy wydrukować ostatnie xlinie. tail -n 5dałoby ci 5 ostatnich linii danych wejściowych. +Rodzaju znakiem odwraca argument i dokonać tailwydruku cokolwiek ale pierwsze x-1linie. tail -n +1wypisałby cały plik, tail -n +2wszystko oprócz pierwszej linii itp.

GNU tailjest znacznie szybszy niż sed. tailjest również dostępny w BSD, a -n +2flaga jest spójna dla obu narzędzi. Sprawdź strony podręcznika FreeBSD lub OS X, aby uzyskać więcej.

Wersja BSD może być jednak znacznie wolniejsza niż sed. Zastanawiam się, jak im się to udało; tailpowinien po prostu czytać plik linia po linii, podczas gdy sedwykonuje dość złożone operacje obejmujące interpretację skryptu, stosowanie wyrażeń regularnych i tym podobne.

Uwaga: możesz ulec pokusie użycia

# THIS WILL GIVE YOU AN EMPTY FILE!
tail -n +2 "$FILE" > "$FILE"

ale to da ci pusty plik . Powodem jest to, że przekierowanie ( >) następuje zanim tailzostanie wywołane przez powłokę:

  1. Shell obcina plik $FILE
  2. Shell tworzy nowy proces dla tail
  3. Shell przekierowuje standardowe wyjście tailprocesu na$FILE
  4. tail czyta z teraz pustego $FILE

Jeśli chcesz usunąć pierwszy wiersz w pliku, powinieneś użyć:

tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE"

&&Będzie upewnić się, że plik nie nadpisywane gdy pojawia się problem.

Aaron Digulla
źródło
3
Zgodnie z tym ss64.com/bash/tail.html typowy bufor domyślnie przyjmuje wartość 32k, gdy używa się BSD „tail” z -ropcją. Może gdzieś w systemie jest ustawiony bufor? A -nmoże 32-bitowy numer ze znakiem?
Yzmir Ramirez
41
@Eddie: user869097 powiedział, że nie działa, gdy pojedyncza linia ma 15 Mb lub więcej. Tak długo, jak linie są krótsze, tailbędzie działać dla dowolnego rozmiaru pliku.
Aaron Digulla
6
czy możesz wyjaśnić te argumenty?
Dreampuf,
17
@Dreampuf - ze strony podręcznika:-n N means output the last N lines, instead of the last 10; or use +N to output lines starting with the Nth
Will Sheppard
11
Chciałem się zgodzić z @JonaChristopherSahnwaldt - ogon jest o wiele, znacznie wolniejszy niż wariant sed, o rząd wielkości. Testuję to na pliku 500 000 KB linii (nie więcej niż 50 znaków na linię). Jednak potem zdałem sobie sprawę, że korzystam z wersji tail (FreeBSD) tail (która domyślnie jest dostarczana z OS X). Kiedy przełączyłem się na ogon GNU, wywołanie ogona było 10 razy szybsze niż wywołanie sed (a także wywołanie sed GNU). AaronDigulla ma rację tutaj, jeśli używasz GNU.
Dan Nguyen,
179

Możesz użyć -i, aby zaktualizować plik bez użycia operatora „>”. Następujące polecenie usunie pierwszy wiersz z pliku i zapisze go w pliku.

sed -i '1d' filename
amit
źródło
1
Pojawia się błąd:unterminated transform source string
Daniel Kobe,
10
działa to za każdym razem i powinno być naprawdę najlepszą odpowiedzią!
xtheking
4
Dla przypomnienia, Mac wymaga podania sufiksu podczas używania sed z edycjami w miejscu. Więc uruchom powyższe z -i.bak
mjp
3
Tylko uwaga - aby usunąć kilka wierszy użyjsed -i '1,2d' filename
Ojciec chrzestny
4
Ta wersja jest naprawdę dużo bardziej czytelna i bardziej uniwersalna niż tail -n +2. Nie jestem pewien, dlaczego nie jest to najlepsza odpowiedź.
Luke Davis
74

Dla tych, którzy korzystają z SunOS, który nie jest GNU, pomoże następujący kod:

sed '1d' test.dat > tmp.dat 
Nasri Najib
źródło
18
Ciekawe dane demograficzne
kapitan
17

Nie, to mniej więcej tak wydajne, jak chcesz. Możesz napisać program C, który mógłby wykonać zadanie nieco szybciej (mniej czasu uruchamiania i przetwarzania argumentów), ale prawdopodobnie będzie dążył do tej samej prędkości co sed, gdy pliki stają się duże (i zakładam, że są duże, jeśli zajmuje to minutę ).

Ale twoje pytanie cierpi z powodu tego samego problemu, co wielu innych, ponieważ z góry zakłada rozwiązanie. Jeśli chcesz nam szczegółowo powiedzieć, co chcesz zrobić, a następnie jak , możemy zaproponować lepszą opcję.

Na przykład, jeśli jest to plik A, który przetwarza inny program B, jednym rozwiązaniem byłoby nie usunięcie pierwszego wiersza, ale zmodyfikowanie programu B, aby przetwarzał go inaczej.

Powiedzmy, że wszystkie twoje programy dołączają się do tego pliku A, a program B odczytuje i przetwarza pierwszy wiersz przed jego usunięciem.

Możesz przeprojektować program B, aby nie próbował usunąć pierwszego wiersza, ale zachował trwałe (prawdopodobnie oparte na plikach) przesunięcie do pliku A, aby przy następnym uruchomieniu mógł szukać tego przesunięcia, przetworzyć linię tam i zaktualizuj przesunięcie.

Następnie, w spokojnym czasie (północ?), Mógłby wykonać specjalne przetwarzanie pliku A, aby usunąć wszystkie aktualnie przetwarzane linie i ustawić przesunięcie z powrotem na 0.

Z pewnością szybsze będzie otwieranie programu i wyszukiwanie pliku niż otwieranie i przepisywanie. Ta dyskusja zakłada oczywiście, że masz kontrolę nad programem B. Nie wiem, czy tak jest, ale mogą istnieć inne możliwe rozwiązania, jeśli przekażesz dodatkowe informacje.

paxdiablo
źródło
Myślę, że PO próbuje osiągnąć to, co sprawiło, że znalazłem to pytanie. Mam 10 plików CSV z 500k linii w każdym. Każdy plik ma ten sam wiersz nagłówka co pierwszy wiersz. Jestem cat: łącząc te pliki w jeden plik, a następnie importując je do DB, pozwalając DB tworzyć nazwy kolumn z pierwszego wiersza. Oczywiście nie chcę powtarzania tej linii w pliku 2-10.
db
1
@db W takim przypadku awk FNR-1 *.csvjest prawdopodobnie szybszy.
jinawee
10

Państwo może edytować pliki w kolejności: wystarczy użyć Perl -iflagę tak:

perl -ni -e 'print unless $. == 1' filename.txt

Powoduje to, że pierwsza linia znika, tak jak pytasz. Perl będzie musiał przeczytać i skopiować cały plik, ale ustawia zapis danych wyjściowych pod nazwą oryginalnego pliku.

Alexis
źródło
10

Możesz to łatwo zrobić za pomocą:

cat filename | sed 1d > filename_without_first_line

w wierszu poleceń; lub aby trwale usunąć pierwszą linię pliku, użyj trybu lokalnego z sed z -iflagą:

sed -i 1d <filename>
Ingo Baab
źródło
9

Jak powiedział Pax, prawdopodobnie nie będziesz szybciej niż to. Powodem jest to, że prawie nie ma systemów plików obsługujących obcinanie od początku pliku, więc będzie to noperacja O ( ), w której njest rozmiar pliku. Tym, co możesz zrobić znacznie szybciej, jest zastąpienie pierwszego wiersza tą samą liczbą bajtów (być może ze spacjami lub komentarzem), co może działać dla Ciebie w zależności od tego, co próbujesz zrobić (co przy okazji?).

Robert Gamble
źródło
Re „... prawie nie ma systemów plików obsługujących obcinanie ...” : to interesujące; prosimy o uwzględnienie nawiasów okrągłych określających taki system plików.
agc
1
@agc: teraz nie ma znaczenia, ale moja pierwsza praca w latach 70. dotyczyła Quadex, małego startupu (już go nie ma i nie ma związku z dwiema firmami, które używają teraz tej nazwy). Mieli system plików, który pozwalał dodawać lub usuwać na początku lub na końcu pliku, używany głównie do implementacji edycji w mniej niż 3 KB, umieszczając pliki nad oknem i pod oknem. Nie miał własnej nazwy, był tylko częścią QMOS, systemu operacyjnego Quadex Multiuser. („Multi” to zwykle 2-3 na LSI-11/02 z mniej niż 64 KB pamięci RAM i zwykle kilka dyskietek 8 "typu RX01 każdy po 250 KB.) :-)
dave_thompson_085
9

spongeUtil unika konieczności żonglowania pliku tymczasowego:

tail -n +2 "$FILE" | sponge "$FILE"
agc
źródło
spongejest rzeczywiście znacznie czystszy i bardziej niezawodny niż przyjęte rozwiązanie ( tail -n +2 "$FILE" > "$FILE.tmp" && mv "$FILE.tmp" "$FILE")
Jealie,
1
Należy wyjaśnić, że „gąbka” wymaga zainstalowania pakietu „moreutils”.
FedFranzoni
To jedyne rozwiązanie, które działało dla mnie, aby zmienić plik systemowy (na obrazie dokera Debiana). Inne rozwiązania nie powiodły się z powodu błędu „Zajęte urządzenie lub zasób” podczas próby zapisu pliku.
FedFranzoni
Ale czy spongebuforuje cały plik w pamięci? To nie zadziała, jeśli będzie to setki GB.
OrangeDog,
@OrangeDog, tak długo, jak system plików może go przechowywać, spongebędzie go wchłaniał, ponieważ używa pliku / tmp jako kroku pośredniego, który jest następnie używany do zastąpienia oryginału.
agc
8

Jeżeli chcesz zmodyfikować plik w miejscu, zawsze można użyć oryginalnego edzamiast swojego s treaming następcy sed:

ed "$FILE" <<<$'1d\nwq\n'

edKomenda był oryginalny edytor tekstu UNIX, zanim nie było nawet terminale pełnoekranowe, stacje robocze znacznie mniej graficznych. exRedaktor, znany jako co używasz podczas wpisywania w okrężnicy szybki w vi, jest ex tendencję wersja ed, więc wiele z tej samej pracy poleceń. Chociaż edma być używany interaktywnie, można go również używać w trybie wsadowym, wysyłając do niego ciąg poleceń, co właśnie robi to rozwiązanie.

Sekwencja <<<$'1d\nwq\n'wykorzystuje wsparcie dla atakujących tutaj-strings ( <<<) i cytaty POSIX ( $'... ') do wejścia zasilającego do edpolecenia składające się z dwóch linii: 1d, która d eletes ustawiają 1 , a następniewq , co wagowo obrzędów plik z powrotem do dysk, a następnie q uits sesję edycji.

Mark Reed
źródło
to jest eleganckie. +1
Armin
Ale musisz wczytać cały plik do pamięci, co nie będzie działać, jeśli będzie to setki GB.
OrangeDog,
5

powinien pokazywać linie oprócz pierwszej linii:

cat textfile.txt | tail -n +2
serup
źródło
4
- powinieneś zrobić „tail -n +2 textfile.txt”
niglesias,
5
@ niglesiais Nie zgadzam się z „bezużytecznym użyciem kota”, ponieważ wyjaśnia, że ​​to rozwiązanie jest odpowiednie w przypadku treści przesyłanych strumieniowo, a nie tylko plików.
Titou
5

Przydałby się do tego vim:

vim -u NONE +'1d' +'wq!' /tmp/test.txt

Powinno to być szybsze, ponieważ vim nie będzie czytał całego pliku podczas przetwarzania.

Hongbo Liu
źródło
Może być konieczne zacytowanie, +wq!czy twoja powłoka jest bash. Prawdopodobnie nie, ponieważ nie !ma go na początku słowa, ale nawyk cytowania rzeczy jest prawdopodobnie dobry. (A jeśli dążysz do super-wydajności, nie przytaczając niepotrzebnie, nie potrzebujesz cytatów wokół 1djednego z nich.)
Mark Reed
vim nie trzeba czytać całego pliku. W rzeczywistości, jeśli plik jest większy niż pamięć, zgodnie z pytaniem w tym Q, vim odczytuje cały plik i zapisuje go (lub większość) do pliku tymczasowego, a po edycji zapisuje wszystko z powrotem (do pliku stałego). Nie wiem, jak według ciebie mogłoby to działać bez tego.
dave_thompson_085
4

Co powiesz na używanie csplit?

man csplit
csplit -k file 1 '{1}'
Shahbaz
źródło
Składnia to także praca, ale tylko wygenerować dwa pliki wyjściowe zamiast trzech: csplit file /^.*$/1. Albo prościej: csplit file //1. Albo jeszcze prościej: csplit file 2.
Marco Roy
1

Ponieważ wygląda na to, że nie mogę przyspieszyć usuwania, myślę, że dobrym rozwiązaniem może być przetworzenie pliku w partiach takich jak ten:

While file1 not empty
  file2 = head -n1000 file1
  process file2
  sed -i -e "1000d" file1
end

Wadą tego jest to, że jeśli program zostanie zabity w środku (lub jeśli jest tam trochę złego sql - powodując śmierć lub blokowanie części „procesu”), pojawią się wiersze, które są pomijane lub przetwarzane dwukrotnie .

(plik1 zawiera wiersze kodu SQL)

Brent
źródło
Co zawiera pierwszy wiersz? Czy możesz po prostu zastąpić go komentarzem sql, jak zasugerowałem w moim poście?
Robert Gamble,
0

Jeśli chcesz odzyskać po awarii, możesz po prostu skompilować plik z tym, co zrobiłeś do tej pory.

if [[ -f $tmpf ]] ; then
    rm -f $tmpf
fi
cat $srcf |
    while read line ; do
        # process line
        echo "$line" >> $tmpf
    done
Tim
źródło
0

Ta jedna wkładka wykona:

echo "$(tail -n +2 "$FILE")" > "$FILE"

Działa, ponieważ plik tailjest wykonywany przed, echoa następnie plik jest odblokowany, dlatego nie jest potrzebny plik tymczasowy.

egors
źródło
-1

Czy wykonanie ogona w wierszach N-1 i przekierowanie go do pliku, a następnie usunięcie starego pliku i zmiana nazwy nowego pliku na starą nazwę, wystarczy?

Gdybym robił to programowo, czytałbym plik i pamiętał przesunięcie pliku, po przeczytaniu każdej linii, więc mogłem wrócić do tej pozycji, aby odczytać plik z jedną linią mniejszą.

EvilTeach
źródło
Pierwsze rozwiązanie jest zasadniczo identyczne z tym, co robi teraz Brent. Nie rozumiem twojego podejścia programowego, tylko pierwszy wiersz musi zostać usunięty, wystarczy przeczytać i odrzucić pierwszy wiersz i skopiować resztę do innego pliku, który jest znowu taki sam jak podejście sed i tail.
Robert Gamble,
Drugie rozwiązanie sugeruje, że plik nie jest za każdym razem zmniejszany o pierwszą linię. Program po prostu przetwarza go, jakby został zmniejszony, ale za każdym razem zaczyna od następnego wiersza
EvilTeach
Nadal nie rozumiem, jakie jest twoje drugie rozwiązanie.
Robert Gamble,