Jak zachować tylko każdą n-tą linię pliku

71

Mam dość duży plik CSV (75 MB). Po prostu próbuję stworzyć jego wykres, więc naprawdę nie potrzebuję wszystkich danych.

Rewording: Chciałbym usunąć n linii, następnie zachować jedną linię, następnie n usunąć linie i tak dalej.

Więc jeśli plik wyglądał tak:

Line 1
Line 2
Line 3
Line 4
Line 5
Line 6

i n = 2, to wynik będzie:

Line 3
Line 6

Wygląda sedna to, że mógłbym to zrobić, ale nie byłem w stanie dowiedzieć się, jak to zrobić. Polecenie bash byłoby idealne, ale jestem otwarty na każde rozwiązanie.

Komputerowy
źródło
2
Czy naprawdę chcesz wiersze 1, 3, 6 itd., A nie 1, 4, 7 itd.?
Ilmari Karonen
2
Ponieważ jest to plik CSV, zakładam, że pierwszy wiersz zawiera metadane (tj. Nazwy pól). Jeśli tak, pytanie powinno brzmieć „co n-ty wiersz po pierwszym”.
iglvzx
7
1, 3, 6 nadal nie ma sensu!
wim
1
Chyba powinien wynosić 1, 3, 5, chyba że n = 2, to wartość liczby magiczne trójkątne (1, 3, 6, 10, 15, 21 itd.)
rjmunro
4
Czy potrafisz zaktualizować swoje pytanie, aby zapewnić spójność tego, o co prosisz („co n-ty wiersz”, „n = 2”) i pożądany wynik (wiersz 3, wiersz 6)? Przyszli czytelnicy będą zdezorientowani.
Keith Thompson,

Odpowiedzi:

121
~ $ awk 'NR == 1 || NR % 3 == 0' yourfile
Line 1
Line 3
Line 6

NR(liczba rekordów) zmienna jest liczbą rekordów, ponieważ domyślnym zachowaniem jest nowa linia dla RS(separator rekordów). wzorzec i akcja są opcjonalne w domyślnym formacie awk 'pattern {actions}'. kiedy dajemy tylko część wzoru, awkzapisuje wszystkie pola $0dla truewarunków naszego wzoru .

Selman Ulug
źródło
8
Dzięki domyślnym ustawieniom nie potrzebujesz aż tak bardzo:awk 'NR == 1 || NR % 3 == 0'
Kevin
@selman: Jeśli podoba Ci się rozwiązanie Kevina, możesz rozważyć aktualizację swojej odpowiedzi.
Keith Thompson
4
Chcesz wyjaśnić, dlaczego tak się dzieje? W ten sposób, jeśli ktoś chce go nieco ulepszyć, to mam nadzieję, że twoje wyjaśnienie mu to pomoże
Ivo Flipse
Odkryłem, że takie podejście pozostawia mi linie 1 i 2 nietknięte. Potwierdza to awk 'NR == 1 || NR % 2 == 0' myfile.txt | wc -lwynik nieparzystej liczby, podczas gdy oryginalny plik miał parzystą liczbę wierszy. Odpowiedź @kev działa najlepiej w moim przypadku testowym.
Daniel Da Cunha,
58

sed może również to zrobić:

$ sed -n '1p;0~3p' input.txt
Line 1
Line 3
Line 6

man sedwyjaśnia ~jako:

first ~ step Dopasuj co krok linię od pierwszej. Na przykład, `` sed -n 1 ~ 2p '' wypisze wszystkie nieparzyste linie w strumieniu wejściowym, a adres 2 ~ 5 będzie pasował co piątą linię, zaczynając od drugiej. pierwszy może wynosić zero; w tym przypadku sed działa tak, jakby był równy krokowi. (To jest rozszerzenie.)

kev
źródło
6
Czy możesz wyjaśnić to polecenie?
qed
1
@qed Objaśnienie: 1pdrukuje pierwszą linię, 0~3pdrukuje co trzecią linię, zaczynając od linii 3 ( 1pjest to zatem wymagane, aby wydrukować linię 1). Pamiętaj jednak, że 0~3nie jest to standard, ale rozszerzenie GNU sed.
Arkku
„To jest rozszerzenie”. Z której wersji korzystasz / korzystałeś?
Victor,
Ta odpowiedź bardzo mi pomogła w Windows PowerShell. Rozszerzyłem to w ten sposób: sed -n '1p;0~10p' '.\in.txt' > out.txtaby wydrukować zredukowany plik do pliku wyjściowego.
kimliv
22

Perl też może to zrobić:

while (<>) {
    print  if $. % 3 == 1;
}

Ten program wypisze pierwszy wiersz swojego wejścia, a następnie co trzeci wiersz.

Aby to trochę wyjaśnić, <>to operator wprowadzania linii, który iteruje po liniach wprowadzania, gdy jest używany w takiej whilepętli. Zmienna specjalna $.zawiera liczbę odczytanych do tej pory wierszy i %jest operatorem modułu.

Ten kod można zapisać jeszcze bardziej kompaktowo jako jeden wiersz, używając przełączników -ni -e:

perl -ne 'print if $. % 3 == 1'  < input.txt  > output.txt

-eWyłącznik przyjmuje część kodu Perl wykonać jako parametr linii polecenia, podczas gdy -nprzełącznik pośrednio owija kodu w whilepętli, jak pokazany powyżej.


Edit: rzeczywiście dostać linii 1, 3, 6, 9, ... jak w tym przykładzie, zamiast linii 1, 4, 7, 10, ..., jak po raz pierwszy przyjęto chciał wymienić $. % 3 == 1się $. == 1 or $. % 3 == 0.

Ilmari Karonen
źródło
7

Jeśli chcesz to zrobić za pomocą skryptu Bash , możesz spróbować:

#!/bin/sh

echo Please enter the file name
read fname
echo Please enter the Nth lines that you want to keep
read n

exec<$fname
value=0
while read line
do
    if [ $(( $value % $n )) -eq 0 ] ; then
        echo -e "$line" >> new_file.txt
    fi
        let value=value+1 
done
echo "Check the 'new_file.txt' that has been created in this directory";

Zapisz go jako „read_lines.sh” i pamiętaj, aby dać + x uprawnienia do pliku bash.

chmod +x ./read_lines.sh
akarpovsky
źródło
1
Jeśli sprawiłeś, że to po prostu emituje na standardowe wyjście, przeczytaj liczbę wierszy, aby pominąć argumenty i przeczytaj plik ze standardowego wejścia, byłoby to prostsze i bardziej przydatne. Nadal możesz utworzyć plik new_file.txt, wykonując ./read_lines.sh > new_file.txt.
rjmunro,
4

Rozwiązaniem w czystym bashu, które nie odradza procesu jest:

{ for f in {1..2}; do read line; done;
  while read line; do
    echo $line;
    for f in {1..2}; do read line; done;
  done; } < file

Pierwszy wiersz pomija 2 linie na początku pliku, a następnie whiledrukuje następny wiersz i ponownie pomija 2 linie.

Jeśli twój plik jest mały, jest to bardzo wydajny sposób wykonania zadania, ponieważ nie uruchamia procesu. Gdy plik jest duży, sednależy go użyć, ponieważ jest bardziej wydajny w przetwarzaniu io bash.

jfg956
źródło
1

Wersja Python (zarówno Python 2, jak i Python 3):

python2 -c "print(''.join(open('file.txt').readlines()[::3]))"

zamień na parametry [::3]początkowe, końcowe i krokowe, aby uzyskać większą kontrolę. Np. [10:36:5]Wypisuje linie 10,15, ..., 35.

Uwaga: ponieważ readlines()zachowuje zakończenia linii, wynik tego wywołania może kończyć się pustą ostatnią linią, chyba że pierwotna ostatnia linia zostanie wyparta przez wybrany rozmiar kroku.

Możliwa jest również wersja strumienia (tutaj wyjście tylko po zakończeniu strumienia):

python -c "import sys;print(''.join(list(sys.stdin)[::3]))" < file.txt
DomTomCat
źródło