Mam wiele plików tekstowych w katalogu i chcę usunąć ostatnią linię każdego pliku w katalogu.
Jak mogę to zrobić?
Mam wiele plików tekstowych w katalogu i chcę usunąć ostatnią linię każdego pliku w katalogu.
Jak mogę to zrobić?
Odpowiedzi:
Możesz użyć tego przyjemnego onelinera, jeśli masz GNU sed
.
sed -i '$ d' ./*
Spowoduje to usunięcie ostatniego wiersza każdego nie ukrytego pliku w bieżącym katalogu. Przełącznik -i
GNU sed
oznacza działanie w miejscu i '$ d'
nakazuje sed
usunięcie ostatniego wiersza ( $
co oznacza ostatnie i d
oznacza usunięcie).
-i
GNUizmu, więc jest to dyskusja, ale straciłbym swoją starą brodę, gdybym nie zauważył, że niektóre starsze wersje sed
nie pozwalają na umieszczanie białych znaków pomiędzy $
i d
(lub, ogólnie między wzorcem a poleceniem).
*(.)
do globowania dla zwykłych plików, nie wiem o innych powłokach.
Wszystkie pozostałe odpowiedzi mają problemy, jeśli katalog zawiera coś innego niż zwykły plik lub plik ze spacjami / znakami nowej nazwy. Oto coś, co działa niezależnie:
find "$dir" -type f -exec sed -i '$d' '{}' '+'
find "$dir" -type f
: znajdź pliki w katalogu $dir
-type f
które są zwykłymi plikami;-exec
wykonaj polecenie dla każdego znalezionego plikused -i
: edytuj pliki na miejscu;'$d'
: usuń ( d
) ostatnią ( $
) linię.'+'
: każe sed
findowi dodawać argumenty do (nieco bardziej wydajne niż uruchamianie polecenia dla każdego pliku osobno, dzięki @zwol).Jeśli nie chcesz schodzić do podkatalogów, możesz dodać argument -maxdepth 1
do find
.
find
jest to bardziej efektywnie napisane find $dir -type f -exec sed -i '$d' '{}' '+'
.)
-print0
nie występuje w pełnym poleceniu, po co to wyjaśniać?
-depth 0
nie działa (findutils 4.4.2), powinno być -maxdepth 1
.
-exec
.
Korzystanie z GNU sed -i '$d'
oznacza odczytanie pełnego pliku i zrobienie jego kopii bez ostatniego wiersza, podczas gdy o wiele bardziej efektywne byłoby po prostu obcięcie pliku na miejscu (przynajmniej w przypadku dużych plików).
Dzięki GNU truncate
możesz:
for file in ./*; do
[ -f "$file" ] &&
length=$(tail -n 1 "$file" | wc -c) &&
[ "$length" -gt 0 ] &&
truncate -s "-$length" "$file"
done
Jeśli pliki są stosunkowo małe, prawdopodobnie byłoby to mniej wydajne, ponieważ uruchamia kilka poleceń na plik.
Zauważ, że w przypadku plików, które zawierają dodatkowe bajty po ostatnim znaku nowej linii (po ostatnim wierszu) lub innymi słowy, jeśli mają nieograniczoną linię ostatniego wiersza , wówczas w zależności od tail
implementacji tail -n 1
zwrócą tylko te dodatkowe bajty (jak GNU tail
), lub ostatnia (właściwie rozdzielona) linia i te dodatkowe bajty.
|wc -c
w tail
rozmowie? (lub a ${#length}
)
${#length}
nie działałby, ponieważ zlicza znaki, a nie bajty i $(...)
usuwałby znak nowego wiersza, więc ${#...}
byłby wyłączony o jeden, nawet gdyby wszystkie znaki były jednobajtowe.
Bardziej przenośne podejście:
for f in ./*
do
test -f "$f" && ed -s "$f" <<\IN
d
w
q
IN
done
Nie sądzę, żeby to wymagało wyjaśnienia ... z wyjątkiem tego, że w tym przypadku d
jest to samo, $d
ponieważ ed
domyślnie wybiera ostatnią linię.
To nie będzie wyszukiwać rekurencyjnie i nie będzie przetwarzać ukrytych plików (inaczej dotfiles).
Jeśli chcesz je edytować, zobacz Jak dopasować * do ukrytych plików w katalogu
[[]]
na, []
to będzie on w pełni zgodny z POSIX. ( [[ ... ]]
to bashism.)
[[
to nie jest bashism )
Jednowarstwowy zgodny z POSIX dla wszystkich plików rozpoczynających się rekurencyjnie w bieżącym katalogu, w tym plików kropkowych:
find . -type f -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Tylko dla .txt
plików, nie rekurencyjnie:
find . -path '*/*/*' -prune -o -type f -name '*.txt' -exec sh -c 'for f; do printf "\$d\nx\n" | ex "$f"; done' sh {} +
Zobacz także: