Usuń ostatnią linię z pliku w Bash

330

Mam plik foo.txtzawierający następujące wiersze:

a
b
c

Chcę prostego polecenia, które powoduje zawartość foo.txt:

a
b
Fragsworth
źródło

Odpowiedzi:

406

Używanie GNU sed:

sed -i '$ d' foo.txt

-iOpcja nie istnieje w GNU sedwersjach starszych niż 3,95, więc trzeba używać go jako filtr z pliku tymczasowego:

cp foo.txt foo.txt.tmp
sed '$ d' foo.txt.tmp > foo.txt
rm -f foo.txt.tmp

Oczywiście w takim przypadku możesz również użyć head -n -1zamiast sed.

System operacyjny Mac:

W systemie Mac OS X (od 10.7.4) odpowiednikiem sed -ipowyższego polecenia jest

sed -i '' -e '$ d' foo.txt
thkala
źródło
56
W systemie Mac OS X (od 10.7.4) odpowiednikiem sed -ipowyższego polecenia jest sed -i '' -e '$ d' foo.txt. Ponadto head -n -1obecnie nie działa na komputerze Mac.
mklement0
26
Czy możesz wyjaśnić, co robi wyrażenie regularne „$ d”? To pytanie o usunięcie ostatniej linii, więc myślę, że jest to najważniejsza część dla każdego, kto ogląda to pytanie. Dzięki :)
kravemir
38
@Miro: W żadnym wypadku nie jest $ dwyrażeniem regularnym. To rozkaz sed. dto polecenie służące do usunięcia wiersza, natomiast $oznacza „ostatni wiersz w pliku”. Podczas określania lokalizacji (zwanej „zasięgiem” w sed lingo) przed poleceniem, polecenie to jest stosowane tylko do określonej lokalizacji. Zatem to polecenie wyraźnie mówi „w zakresie ostatniego wiersza w pliku, usuń go”. Całkiem sprytnie i od razu do rzeczy, jeśli mnie pytasz.
Daniel Kamil Kozar
1
Jak usunąć ostatni wiersz, ale tylko jeśli jest to pusty wiersz?
skan
1
@Alex: zaktualizowano dla GNU sed; nie mam pojęcia o różnych wersjach BSD / UNIX / MacOS ...
thkala
279

Jest to zdecydowanie najszybsze i najprostsze rozwiązanie, szczególnie w przypadku dużych plików:

head -n -1 foo.txt > temp.txt ; mv temp.txt foo.txt

jeśli chcesz usunąć górny wiersz, użyj tego:

tail -n +2 foo.txt

co oznacza linie wyjściowe zaczynające się od linii 2.

Nie używaj seddo usuwania linii z góry lub z dołu pliku - jest bardzo bardzo wolny, jeśli plik jest duży.

Jan
źródło
16
head -n -1 foo.txtwystarczy
ДМИТРИЙ МАЛИКОВ
20
head -n -1 nie działa na głowie bdsutils. przynajmniej nie w wersji używanej przez macos, więc to nie działa.
johannes_lalala
23
@ johannes_lalala Na Macu możesz brew install coreutils(narzędzia GNU core) i używać gheadzamiast head.
ciekawe,
Oto prosty skrypt bash, który automatyzuje go w przypadku, gdy masz wiele plików z różną liczbą linii u dołu do usunięcia: cat TAILfixer FILE=$1; TAIL=$2; head -n -${TAIL} ${FILE} > temp ; mv temp ${FILE}Uruchom go, aby usunąć 4 linie, na przykład z ./TAILfixer myfile 4chmod +x TAILfixer
mojego
Kciuki w górę, ponieważ zadziałały, ale dla wszystkich porównań sed, to polecenie jest również do cholery powolne
Jeff Diederiks
121

Miałem problem ze wszystkimi odpowiedziami tutaj, ponieważ pracowałem z OGROMNYM plikiem (~ 300 Gb) i żadne z rozwiązań nie skalowało się. Oto moje rozwiązanie:

dd if=/dev/null of=<filename> bs=1 seek=$(echo $(stat --format=%s <filename> ) - $( tail -n1 <filename> | wc -c) | bc )

Innymi słowy: znajdź długość pliku, z którym chcesz skończyć (długość pliku minus długość długości ostatniego wiersza, używając bc) i ustaw tę pozycję na koniec pliku (wprowadzając ddjeden bajt /dev/nullna to).

Jest to szybkie, ponieważ tailzaczyna czytać od końca i ddzastąpi plik na miejscu, zamiast kopiować (i analizować) każdą linię pliku, co robią inne rozwiązania.

UWAGA: To usuwa linię z pliku na miejscu! Utwórz kopię zapasową lub przetestuj plik zastępczy przed wypróbowaniem go na własnym pliku!

Yossi Farjoun
źródło
3
Nawet nie wiedziałem o dd. To powinna być najlepsza odpowiedź. Dziękuję bardzo! Szkoda, że ​​to rozwiązanie nie działa w przypadku (blokowania) plików spakowanych gzip.
tommy.carstensen
4
Bardzo, bardzo sprytne podejście! Tylko uwaga: wymaga i działa na prawdziwym pliku, więc nie można go używać w potokach, podstawieniach poleceń, przekierowaniach i takich łańcuchach.
MestreLion
3
To powinna być najlepsza odpowiedź. Natychmiast pracował dla mojego pliku ~ 8 GB.
Peter Cogan,
8
Użytkownicy komputerów Mac: dd if = / dev / null of = <nazwa pliku> bs = 1 seek = $ (echo $ (stat -f =% z <nazwa pliku> | cut -c 2-) - $ (tail -n1 <nazwa pliku> | wc -c) | bc)
Igor
2
To podejście jest odpowiednie dla dużych plików!
thomas.han
61

Aby usunąć ostatnią linię z pliku bez czytania całego pliku lub przepisywania czegokolwiek , możesz użyć

tail -n 1 "$file" | wc -c | xargs -I {} truncate "$file" -s -{}

Aby usunąć ostatni wiersz i wydrukować go na standardowym wyjściu („pop”), możesz połączyć to polecenie z tee:

tail -n 1 "$file" | tee >(wc -c | xargs -I {} truncate "$file" -s -{})

Te polecenia mogą skutecznie przetwarzać bardzo duży plik. Jest to podobne i inspirowane odpowiedzią Yossi, ale unika korzystania z kilku dodatkowych funkcji.

Jeśli zamierzasz używać ich wielokrotnie i chcesz obsługiwać błędy oraz niektóre inne funkcje, możesz użyć poptailpolecenia tutaj: https://github.com/donm/evenmoreutils

ohspite
źródło
3
Szybka uwaga: truncatedomyślnie nie jest dostępna w systemie OS X. Ale to jest łatwe do zainstalowania za pomocą Brew: brew install truncate.
Guillaume Boudreau
3
Bardzo dobrze. O wiele lepiej bez dd. Byłem przerażony, że użycie dd jako pojedynczej źle umieszczonej cyfry lub litery może oznaczać katastrofę .. +1
Yossi Farjoun
1
(ŻADNE OSTRZEŻENIE) Właściwie („dd” -> „katastrofa”) wymaga 1 zamiany i 6 wstawień; ledwie „jedna źle umieszczona cyfra lub litera”. :)
Kyle
29

Użytkownicy komputerów Mac

jeśli chcesz tylko usunąć wyjście z ostatniego wiersza bez zmiany samego pliku, wykonaj

sed -e '$ d' foo.txt

jeśli chcesz usunąć ostatni wiersz samego pliku wejściowego, zrób to

sed -i '' -e '$ d' foo.txt

manpreet singh
źródło
To działa dla mnie, ale nie rozumiem. w każdym razie zadziałało.
Melvin,
Flaga -i ''mówi sedowi, aby zmodyfikował plik w miejscu, zachowując kopię zapasową w pliku z rozszerzeniem podanym jako parametr. Ponieważ parametr jest pustym ciągiem, plik kopii zapasowej nie jest tworzony. -ekaże sedowi wykonać polecenie. Polecenie $ doznacza: znajdź ostatni wiersz ( $) i usuń go ( d).
Krzysztof Szafranek
24

Użytkownicy komputerów Mac:

Na Macu, head -n -1 nie będzie działać. I próbowałem znaleźć proste rozwiązanie (nie martwiąc się czasem przetwarzania), aby rozwiązać ten problem tylko za pomocą poleceń „głowa” i / lub „ogon”.

Wypróbowałem następującą sekwencję poleceń i cieszę się, że mogłem ją rozwiązać za pomocą polecenia „tail” [z opcjami dostępnymi na Macu]. Tak więc, jeśli jesteś na komputerze Mac i chcesz użyć tylko „ogona”, aby rozwiązać ten problem, możesz użyć tego polecenia:

plik cat.txt | ogon -r | ogon -n +2 | ogon -r

Objaśnienie:

1> tail -r: po prostu odwraca kolejność linii na wejściu

2> tail -n +2: drukuje wszystkie linie zaczynające się od drugiej linii na wejściu

Sarfraaz Ahmed
źródło
2
Zainstalowanie coreutils za pomocą Homebrew da GNU głowę jako „ghead”, pozwalając ci to zrobićghead -n -1
nieco
13
echo -e '$d\nw\nq'| ed foo.txt
lhf
źródło
2
W przypadku rozwiązania filtrującego, które nie edytuje pliku na miejscu, użyj sed '$d'.
lhf
8
awk 'NR>1{print buf}{buf = $0}'

Zasadniczo ten kod mówi:

Dla każdej linii po pierwszej wydrukuj buforowaną linię

dla każdej linii zresetuj bufor

Bufor jest opóźniony o jedną linię, stąd kończy się drukowanie linii od 1 do n-1

Foo Bah
źródło
2
To rozwiązanie jest filtrem i nie edytuje pliku na miejscu.
lhf
0
awk "NR != `wc -l < text.file`" text.file |> text.file

Ten fragment kodu załatwia sprawę.

Michał Kingski
źródło
To po prostu usuwa dla mnie cały plik.
Kartik Chugh
0

Oba te rozwiązania są tutaj w innych formach. Uważam, że są one trochę bardziej praktyczne, jasne i przydatne:

Za pomocą dd:

BADLINESCOUNT=1
ORIGINALFILE=/tmp/whatever
dd if=${ORIGINALFILE} of=${ORIGINALFILE}.tmp status=none bs=1 count=$(printf "$(stat --format=%s ${ORIGINALFILE}) - $(tail -n${BADLINESCOUNT} ${ORIGINALFILE} | wc -c)\n" | bc )
/bin/mv -f ${ORIGINALFILE}.tmp ${ORIGINALFILE}

Za pomocą obcięcia:

BADLINESCOUNT=1
ORIGINALFILE=/tmp/whatever
truncate -s $(printf "$(stat --format=%s ${ORIGINALFILE}) - $(tail -n${BADLINESCOUNT} ${ORIGINALFILE} | wc -c)\n" | bc ) ${ORIGINALFILE}
wirtualne światło
źródło
Auć. Zbyt skomplikowane.
Robert
0

Linux

$ jest ostatnim wierszem, d do usunięcia:

sed '$d' ~/path/to/your/file/name

System operacyjny Mac

Odpowiednik sed--i

sed -i '' -e '$ d' ~/path/to/your/file/name
jasonleonhard
źródło
0

Oto, jak możesz to zrobić ręcznie (osobiście często używam tej metody, gdy muszę szybko usunąć ostatnią linię w pliku):

vim + [FILE]

Ten +znak oznacza, że ​​gdy plik zostanie otwarty w edytorze tekstów vim, kursor zostanie umieszczony w ostatnim wierszu pliku.

Teraz naciśnij ddwa razy na klawiaturze. To zrobi dokładnie to, co chcesz - usuń ostatni wiersz. Następnie naciśnij, :a xnastępnie naciśnij Enter. Spowoduje to zapisanie zmian i powrót do powłoki. Teraz ostatni wiersz został pomyślnie usunięty.

Michael Rybkin
źródło
2
Nacisk na prostotę został utracony tutaj
Peter M
-4

Rubin (1.9+)

ruby -ne 'BEGIN{prv=""};print prv ; prv=$_;' file
kurumi
źródło