Jak obciąć plik wierszami?

13

Mam dużą liczbę plików, z których niektóre są bardzo długie. Chciałbym je przyciąć do określonego rozmiaru, jeśli są większe, usuwając koniec pliku. Ale chcę tylko usunąć całe linie. W jaki sposób mogę to zrobić? To wydaje się być czymś, co poradziłby sobie system narzędziowy Linuksa, ale nie znam właściwego polecenia.

Załóżmy na przykład, że mam plik o wielkości 120 000 bajtów z liniami o długości 300 bajtów i próbuję go skrócić do 10 000 bajtów. Pierwsze 33 wiersze powinny pozostać (9900 bajtów), a pozostałe powinny zostać wycięte. Nie chcę przecinać dokładnie 10 000 bajtów, ponieważ pozostawiłoby to częściową linię.

Oczywiście pliki mają różną długość, a linie nie są tej samej długości.

Idealnie pliki wynikowe byłyby nieco krótsze niż nieco dłuższe (jeśli punkt przerwania znajduje się na długiej linii), ale to nie jest zbyt ważne, może to być nieco dłuższe, gdyby to „łatwiejsze”. Chciałbym, aby zmiany były wprowadzane bezpośrednio w plikach (no cóż, być może nowy plik skopiowany gdzie indziej, oryginalny usunięty, a nowy plik przeniesiony, ale to samo z POV użytkownika). Rozwiązanie, które przekierowuje dane do wielu miejsc, a następnie z powrotem zachęca do uszkodzenia pliku i chciałbym tego uniknąć ...

Charles
źródło
Usunąłem swoją odpowiedź… Myślę, że rozmiar pliku w Bajtach nie był zbyt jasny, przepraszam. Może mógłbyś edytować swoje pytanie i wyjaśnić tę część (np. Z przykładem)?
slhck
@slhck: Przepraszam, że straciłeś przedstawiciela tylko dlatego, że byłem niejasny ... pozwól mi zobaczyć, czy mogę to naprawić.
Charles
Nie martw się, powinienem tylko zapytać, przepraszam :)
slhck

Odpowiedzi:

1

sed/ wcZłożoność można uniknąć w poprzednich odpowiedzi, jeśli awkjest używany. Korzystając z przykładu dostarczonego z OP (pokazującego pełne linie przed 10000 bajtów):

awk '{i += (length() + 1); if (i <= 10000) print $ALL}' myfile.txt

Pokazuje również pełny wiersz zawierający 10000. bajt, jeśli ten bajt nie znajduje się na końcu wiersza:

awk '{i += (length() + 1); print $ALL; if (i >= 10000) exit}' myfile.txt

Powyższa odpowiedź zakłada:

  1. Pliki tekstowe mają uniksowy terminator linii ( \n). W przypadku plików tekstowych Dos / Windows ( \r\n) zmień length() + 1nalength() + 2
  2. Plik tekstowy zawiera tylko znak jednobajtowy. Jeśli występuje znak wielobajtowy (na przykład w środowisku Unicode), ustaw środowisko, LC_CTYPE=Caby wymusić interpretację na poziomie bajtów.
Abel Cheung
źródło
15

sedPodejście jest w porządku, ale do pętli na wszystkich liniach nie jest. Jeśli wiesz, ile wierszy chcesz zachować (aby mieć przykład, używam tutaj 99), możesz to zrobić w następujący sposób:

sed -i '100,$ d' myfile.txt

Objaśnienie: sedjest procesorem wyrażeń regularnych. Z -ipodaną opcją przetwarza plik bezpośrednio („inline”) - zamiast po prostu czytać go i zapisywać wyniki na standardowym wyjściu. 100,$oznacza po prostu „od wiersza 100 do końca pliku” - po nim następuje polecenie d, które prawdopodobnie poprawnie odgadłeś jako „usuń”. Krótko mówiąc, polecenie oznacza: „Usuń wszystkie linie z linii 100 do końca pliku z mojego pliku.txt”. 100 to pierwszy wiersz do usunięcia, ponieważ chcesz zachować 99 wierszy.

Edycja: Jeśli z drugiej strony istnieją pliki dziennika, w których chcesz zachować, np. Ostatnie 100 wierszy:

[ $(wc -l myfile.txt) -gt 100 ] && sed -i "1,$(($(wc -l myfile.txt|awk '{print $1}') - 100)) d" myfile.txt

Co tu się dzieje:

  • [ $(wc -l myfile.txt) -gt 100 ]: wykonaj następujące czynności tylko wtedy, gdy plik ma więcej niż 100 linii
  • $((100 - $(wc -l myfile.txt|awk '{print $1}'))): oblicz liczbę linii do usunięcia (tzn. wszystkie linie pliku oprócz (ostatnich) 100 do zachowania)
  • 1, $((..)) d: usuń wszystkie linie od pierwszej do linii obliczonej

EDYCJA: ponieważ pytanie zostało właśnie zredagowane, aby podać więcej szczegółów, do mojej odpowiedzi dołączę również te dodatkowe informacje. Dodano fakty:

  • plik ma określony rozmiar (10 000 bajtów)
  • każda linia ma określony rozmiar w bajtach (300 bajtów w przykładzie)

Na podstawie tych danych można obliczyć liczbę linii, które pozostaną jako „/”, co w przykładzie oznaczałoby 33 linie. Termin powłoki do obliczeń: $((size_to_remain / linesize))(przynajmniej w systemie Linux przy użyciu Bash, wynikiem jest liczba całkowita). Skorygowane polecenie brzmiałoby teraz:

# keep the start of the file (OPs question)
sed -i '34,$ d' myfile.txt
# keep the end of the file (my second example)
[ $(wc -l myfile.txt) -gt 33 ] && sed -i "1,33 d" myfile.txt

Ponieważ rozmiary są znane z góry, nie ma już potrzeby wykonywania obliczeń osadzonych w sedpoleceniu. Ale dla elastyczności w skrypcie powłoki można używać zmiennych.

Do przetwarzania warunkowego opartego na rozmiarze pliku można użyć następującej „testowej” konstrukcji:

[ "$(ls -lk $file | awk ' {print $5}')" -gt 100 ] &&

co oznacza: „jeśli rozmiar $fileprzekracza 100 ls -lkkB , wykonaj ...” ( wyświetla rozmiar pliku w kB w pozycji 5, stąd awkjest używany do wyodrębnienia dokładnie tego).

Izzy
źródło
OP chce wyciąć plik na podstawie określonego rozmiaru bajtu - nie tylko długości pod względem linii. Usunąłem swoją odpowiedź dotyczącą head -n.
slhck
@slhck Dziękujemy za powiadomienie. Tak, OP właśnie zmodyfikował swoje pytanie, aby wyjaśnić ten zamiar. Ponieważ ma on środki do obliczenia, ile bajtów ma każdy wiersz, moja odpowiedź pozostaje zasadniczo ważna - ponieważ może obliczyć liczbę pozostałych wierszy, a następnie użyć mojego podejścia do obsługi plików. Może w mojej odpowiedzi zwrócę na to uwagę.
Izzy
Nie - rozmiary nie są znane z góry. To był przykład. Każdy plik będzie miał inny rozmiar, a linie będą miały nieregularną długość. Niektóre pliki wcale nie muszą być obcinane.
Charles
Och, znowu ... Cóż, niektóre rzeczy trudno wyjaśnić jasno (zbyt wiele facetów). Jeśli chodzi o pliki, które nie wymagają obcięcia, to prawdopodobnie zależy to od rozmiaru pliku? Można to pokryć. Ale jeśli nie jest znany nawet średni rozmiar linii, ta część staje się trudna - w tej chwili nie mogę wymyślić łatwego rozwiązania (bez nadmiernego obciążenia).
Izzy
Wszystko, co mogę obecnie wymyślić, wymagałoby np. Uzyskania pierwszych n wierszy, obliczenia na ich podstawie średniej długości i użycia tej wartości. Czy to by ci pomogło?
Izzy
0

Nie mogąc znaleźć polecenia, aby to zrobić, napisałem szybki skrypt (nie przetestowany):

#!/bin/sh

# Usage: $0 glob.* 25000
# where glob.* is a wildcard pattern and 25000 is the maximum number of bytes.

limit=20000
tmp=/tmp/trim
[[ "$2" == +([0-9]) ]] || limit=$2
limit=`expr $len + 1`
for file in $1;
do
    [[ `wc -c $file` -lt $limit ]] && continue
    head -c $file > $tmp
    sed '$d' $tmp
    $tmp > $file
done
Charles
źródło
-1

Możesz użyć polecenia linux sed, aby usunąć linie z pliku. Następujące polecenie usuwa ostatni wiersz pliku.txt:

sed '$d' filename.txt

Za pomocą awk lub find możesz wyszukać wzorzec pasujący do twojego polecenia sed. Najpierw wyszukaj za pomocą awk lub znajdź pliki, które chcesz skrócić, a następnie możesz usunąć linie za pomocą sed.

kockiren
źródło
-1

Zrobiłem coś podobnego z ogonem. Aby w tym przypadku zachować tylko ostatnie 10 000 wierszy:

TMP=$(tail -n 10000 /path/to/some/file 2>/dev/null) && echo "${TMP}" > /path/to/some/file
Bill M.
źródło