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 -iGNU sedoznacza działanie w miejscu i '$ d'nakazuje sedusunięcie ostatniego wiersza ( $co oznacza ostatnie i doznacza usunięcie).
-iGNUizmu, więc jest to dyskusja, ale straciłbym swoją starą brodę, gdybym nie zauważył, że niektóre starsze wersje sednie 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 sedfindowi 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 1do find.
findjest to bardziej efektywnie napisane find $dir -type f -exec sed -i '$d' '{}' '+'.)
-print0nie występuje w pełnym poleceniu, po co to wyjaśniać?
-depth 0nie 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 truncatemoż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 tailimplementacji tail -n 1zwrócą tylko te dodatkowe bajty (jak GNU tail), lub ostatnia (właściwie rozdzielona) linia i te dodatkowe bajty.
|wc -cw tailrozmowie? (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 djest to samo, $dponieważ eddomyś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 .txtplikó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: