Jak mogę wyodrębnić z góry określony zakres linii z pliku tekstowego w systemie Unix?


531

Mam zrzut SQL ~ 23000 wierszy zawierający dane o wartości kilku baz danych. Muszę wyodrębnić pewną sekcję tego pliku (tj. Dane dla pojedynczej bazy danych) i umieścić ją w nowym pliku. Znam zarówno numer początkowy, jak i końcowy danych, które chcę.

Czy ktoś zna polecenie uniksowe (lub serię poleceń), aby wyodrębnić wszystkie wiersze z pliku między powiedzmy wierszami 16224 i 16482, a następnie przekierować je do nowego pliku?


Ponieważ wspominasz o dużych plikach, sugeruję sprawdzenie komentarza stackoverflow.com/questions/83329/...
— sancho.s ReinstateMonicaCellio

Odpowiedzi:


792
sed -n '16224,16482p;16483q' filename > newfile

Z podręcznika sed :

p - Wydrukuj przestrzeń wzoru (na standardowe wyjście). To polecenie jest zwykle używane tylko w połączeniu z opcją wiersza polecenia -n.

n - Jeśli automatyczne drukowanie nie jest wyłączone, wydrukuj przestrzeń wzoru, a następnie zastąp przestrzeń wzoru następnym wierszem wprowadzania. Jeśli nie ma więcej danych wejściowych, sed kończy pracę bez przetwarzania żadnych poleceń.

q - Wyjdź sedbez przetwarzania żadnych poleceń lub danych wejściowych. Zauważ, że bieżąca przestrzeń wzoru jest drukowana, jeśli automatyczne drukowanie nie jest wyłączone z opcją -n.

i

Adresy w skrypcie sed mogą mieć dowolną z następujących postaci:

liczba Określenie numeru linii będzie pasować tylko do tej linii na wejściu.

Zakres adresów można określić, podając dwa adresy oddzielone przecinkiem (,). Zakres adresów odpowiada wierszom zaczynającym się od miejsca, w którym pasuje pierwszy adres, i trwa aż do dopasowania drugiego adresu (włącznie).


3
Byłem ciekawy, czy to modyfikuje oryginalny plik. Wykonałem kopię zapasową na wszelki wypadek i wygląda na to, że NIE zmodyfikowało to oryginału, zgodnie z oczekiwaniami.
— Andy Groff,

@AndyGroff. Aby zmodyfikować plik, użyj parametru „-i”. W przeciwnym razie plik nie zostanie zmodyfikowany.
— 13

175
Jeśli, podobnie jak ja, musisz to zrobić na BARDZO dużym pliku, pomaga to, jeśli dodasz polecenie zakończenia w następnym wierszu. To jest to sed -n '16224,16482p;16483q' filename. W przeciwnym razie sed będzie skanował do końca (a przynajmniej tak robi moja wersja).
— wds

7
@MilesRout wydaje się pytać „dlaczego głosowanie negatywne?” dość często, może masz na myśli „mnie to nie obchodzi” zamiast „nikogo to nie obchodzi”
— Mark

1
@wds - Twój komentarz zasługuje na odpowiedź, która wspina się na szczyt. To może zrobić różnicę między dniem a nocą.
— sancho.s ReinstateMonicaCellio

203
sed -n '16224,16482 p' orig-data-file > new-file

Gdzie 16224,16482 to numer linii początkowej i numer linii końcowej włącznie. Jest to indeks 1. -ntłumi echo wejścia jako wyjścia, czego wyraźnie nie chcesz; liczby wskazują zakres wierszy, na których działają następujące polecenia; polecenie pwypisuje odpowiednie linie.


7
W przypadku dużych plików powyższe polecenie będzie kontynuowało przechodzenie całego pliku po znalezieniu żądanego zakresu. Czy istnieje sposób, aby sed przestał przetwarzać plik po wyprowadzeniu zakresu?
— Gary

39
Cóż, od odpowiedzi tutaj wydaje się, że zatrzymuje się na końcu zakresu może być realizowane z: sed -n '16224,16482p;16482q' orig-data-file > new-file.
— Gary

5
Dlaczego miałbyś umieszczać niepotrzebne miejsce, a następnie musiałbyś cytować? (Oczywiście, tworzenie niepotrzebnych problemów i ich rozwiązywanie jest istotą połowy informatyki, ale poza tym mam na myśli ...)
— Kaz

92

Całkiem proste użycie głowy / ogona:

head -16482 in.sql | tail -258 > out.sql

przy użyciu sed:

sed -n '16482,16482p' in.sql > out.sql

używając awk:

awk 'NR>=10&&NR<=20' in.sql > out.sql

1
Druga i trzecia opcja są OK, ale pierwsza jest wolniejsza niż wiele alternatyw, ponieważ używa 2 poleceń, gdzie 1 jest wystarczająca. Wymaga również obliczeń, aby uzyskać właściwy argument tail.
— Jonathan Leffler,

3
Warto zauważyć, że aby zachować te same numery wierszy co pytanie, polecenie sed powinno być, sed -n 16224,16482p' in.sql >out.sqla polecenie awk powinno byćawk 'NR>=16224&&NR<=16482' in.sql > out.sql
— sibaz

3
Warto również wiedzieć, że w przypadku pierwszego przykładu head -16482 in.sql | tail -$((16482-16224)) >out.sqlobliczenia pozostawiają bash
— sibaz

1
Pierwszy z głową i ogonem WAYYYY szybciej na dużych plikach niż wersja sed, nawet z dodaną opcją q. wersja head instant i sed wersja I Ctrl-C po minucie ... Dzięki
— Miyagi,

2
tail -n +16224
— Przydałoby się

35

Możesz użyć „vi”, a następnie następującego polecenia:

:16224,16482w!/tmp/some-file

Alternatywnie:

cat file | head -n 16482 | tail -n 258

EDYCJA: - Aby dodać wyjaśnienie, użyj nagłówka -n 16482, aby wyświetlić pierwsze 16482 wiersze, a następnie użyj tail -n 258, aby uzyskać ostatnie 258 wierszy z pierwszego wyjścia.


2
I zamiast vi możesz użyć ex, czyli vi minus interaktywne konsole.
— Tadeusz A. Kadłubowski

1
Nie potrzebujesz catpolecenia; headmoże odczytać plik bezpośrednio. Jest to wolniejsze niż wiele alternatyw, ponieważ wykorzystuje 2 (3 jak pokazano) polecenia, w których 1 jest wystarczające.
— Jonathan Leffler,

1
@JonathanLeffler Mylisz się. Jest niesamowicie szybki. Wyodrębniam 200 tys. Linii, około 1G, z pliku 2G z 500 tys. Linii, w kilka sekund (bez cat). Inne rozwiązania wymagają co najmniej kilku minut. Wydaje się też, że najszybsza odmiana GNU tail -n +XXX filename | head XXX.
— Antonis Christofides,

28

Istnieje inne podejście z awk:

awk 'NR==16224, NR==16482' file

Jeśli plik jest ogromny, dobrze jest exitpo przeczytaniu ostatniego żądanego wiersza. W ten sposób nie będzie niepotrzebnie czytać następujących wierszy:

awk 'NR==16224, NR==16482-1; NR==16482 {print; exit}' file

awk 'NR==16224, NR==16482; NR==16482 {exit}' file

2
1+ do oszczędzania czasu pracy i zasobów przy użyciu print; exit. Dzięki !
— Bernie Reiter

Nieznaczne uproszczenie drugiego przykładu:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
— Robin A. Meade

To jasne, dzięki @ RobinA.Meade! Zredagowałem twój pomysł w post
— fedorqui „SO przestań szkodzić”


9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2

6
cat dump.txt | head -16224 | tail -258

powinien załatwić sprawę. Minusem tego podejścia jest to, że musisz wykonać arytmetykę, aby ustalić argument dla ogona i uwzględnić, czy chcesz, aby „między” obejmował linię końcową, czy nie.


4
Nie potrzebujesz catpolecenia; headmoże odczytać plik bezpośrednio. Jest to wolniejsze niż wiele alternatyw, ponieważ wykorzystuje 2 (3 jak pokazano) polecenia, w których 1 jest wystarczające.
— Jonathan Leffler,

@JonathanLeffler Ta odpowiedź jest najłatwiejsza do odczytania i zapamiętania. Jeśli naprawdę zależy Ci na wydajności, nie używałbyś powłoki. Dobrą praktyką jest pozwalanie określonym narzędziom na poświęcenie się konkretnemu zadaniu. Ponadto „arytmetykę” można rozwiązać za pomocą | tail -$((16482 - 16224)).
— Yeti

6

Stojąc na ramionach boxxar podoba mi się to:

sed -n '<first line>,$p;<last line>q' input

na przykład

sed -n '16224,$p;16482q' input

Te $środki „ostatnia linia”, więc pierwsza komenda powoduje sedwydrukować wszystkie linie zaczynające się na linii 16224i drugich marek dowodzenia sedrzucić po drukowaniu linii 16428. (Dodawanie 1opcji q-range w rozwiązaniu boxxar nie wydaje się konieczne).

Podoba mi się ten wariant, ponieważ nie muszę dwukrotnie podawać numeru linii końcowej. Zmierzyłem, że używanie $nie ma szkodliwego wpływu na wydajność.



3

Szybko i brudno:

head -16428 < file.in | tail -259 > file.out

Prawdopodobnie nie jest to najlepszy sposób, ale powinien działać.

BTW: 259 = 16482-16224 + 1.


Jest to wolniejsze niż wiele alternatyw, ponieważ wykorzystuje 2 polecenia, z których 1 jest wystarczające.
— Jonathan Leffler,

3

Napisałem program Haskell o nazwie splitter, który robi dokładnie to: przeczytaj mój post na blogu o wydaniu .

Możesz użyć programu w następujący sposób:

$ cat somefile | splitter 16224-16482

I to wszystko. Będziesz potrzebował Haskell, aby go zainstalować. Właśnie:

$ cabal install splitter

I gotowe. Mam nadzieję, że ten program okaże się przydatny.


Czy splittertylko do odczytu ze standardowego wejścia? W pewnym sensie nie ma to znaczenia; catkomenda jest zbędny czy to robi lub nie robi. Użyj splitter 16224-16482 < somefilealbo (jeśli pobiera argumenty nazwy pliku) splitter 16224-16482 somefile.
— Jonathan Leffler,

3

Nawet my możemy to zrobić, aby sprawdzić w wierszu polecenia:

cat filename|sed 'n1,n2!d' > abc.txt

Na przykład:

cat foo.pl|sed '100,200!d' > abc.txt

6
Nie potrzebujesz catpolecenia w żadnym z nich; sedjest całkowicie zdolny do samodzielnego odczytu plików lub możesz przekierować standardowe wejście z pliku.
— Jonathan Leffler,

3

Za pomocą ruby:

ruby -ne 'puts "#{$.}: #{$_}" if $. >= 32613500 && $. <= 32614500' < GND.rdf > GND.extract.rdf

2

Już miałem opublikować trik z głową / ogonem, ale tak naprawdę prawdopodobnie po prostu odpalę emacsa. ;-)

  1. esc- xgoto-line ret16224
  2. znak ( ctrl- space)
  3. esc- xgoto-lineret 16482
  4. esc-w

otwórz nowy plik wyjściowy, zapisz ctl-y

Zobaczmy, co się dzieje.


4
Emacs nie działa bardzo dobrze na bardzo dużych plikach z mojego doświadczenia.
— Greg Mattes,

Czy możesz uruchomić to jako działanie skryptowe, czy jest to tylko opcja interaktywna?
— Jonathan Leffler,

2

Użyłbym:

awk 'FNR >= 16224 && FNR <= 16482' my_file > extracted.txt

FNR zawiera numer rekordu (linii) linii odczytywanej z pliku.


2

Chciałem zrobić to samo ze skryptu przy użyciu zmiennej i osiągnąłem to, umieszczając cudzysłowy wokół zmiennej $, aby oddzielić nazwę zmiennej od p:

sed -n "$first","$count"p imagelist.txt >"$imageblock"

Chciałem podzielić listę na osobne foldery, znalazłem wstępne pytanie i odpowiedziałem na użyteczny krok. (polecenie split nie jest opcją w starym systemie operacyjnym, do którego muszę przenieść kod).


1

Napisałem mały skrypt bash, który można uruchomić z wiersza poleceń, o ile aktualizujesz PATH tak, aby zawierał katalog (lub możesz umieścić go w katalogu, który jest już zawarty w PATH).

Zastosowanie: $ pinch nazwa pliku linia początkowa linia końcowa

#!/bin/bash
# Display line number ranges of a file to the terminal.
# Usage: $ pinch filename start-line end-line
# By Evan J. Coon

FILENAME=$1
START=$2
END=$3

ERROR="[PINCH ERROR]"

# Check that the number of arguments is 3
if [ $# -lt 3 ]; then
    echo "$ERROR Need three arguments: Filename Start-line End-line"
    exit 1
fi

# Check that the file exists.
if [ ! -f "$FILENAME" ]; then
    echo -e "$ERROR File does not exist. \n\t$FILENAME"
    exit 1
fi

# Check that start-line is not greater than end-line
if [ "$START" -gt "$END" ]; then
    echo -e "$ERROR Start line is greater than End line."
    exit 1
fi

# Check that start-line is positive.
if [ "$START" -lt 0 ]; then
    echo -e "$ERROR Start line is less than 0."
    exit 1
fi

# Check that end-line is positive.
if [ "$END" -lt 0 ]; then
    echo -e "$ERROR End line is less than 0."
    exit 1
fi

NUMOFLINES=$(wc -l < "$FILENAME")

# Check that end-line is not greater than the number of lines in the file.
if [ "$END" -gt "$NUMOFLINES" ]; then
    echo -e "$ERROR End line is greater than number of lines in file."
    exit 1
fi

# The distance from the end of the file to end-line
ENDDIFF=$(( NUMOFLINES - END ))

# For larger files, this will run more quickly. If the distance from the
# end of the file to the end-line is less than the distance from the
# start of the file to the start-line, then start pinching from the
# bottom as opposed to the top.
if [ "$START" -lt "$ENDDIFF" ]; then
    < "$FILENAME" head -n $END | tail -n +$START
else
    < "$FILENAME" tail -n +$START | head -n $(( END-START+1 ))
fi

# Success
exit 0

1
Jest to wolniejsze niż wiele alternatyw, ponieważ wykorzystuje 2 polecenia, z których 1 jest wystarczające. W rzeczywistości odczytuje plik dwa razy z powodu wcpolecenia, które marnuje przepustowość dysku, szczególnie w przypadku plików gigabajtowych. Pod wieloma względami jest to dobrze udokumentowane, ale jest to również nadmiar umiejętności inżynieryjnych.
— Jonathan Leffler,

1

To może Ci pomóc (GNU sed):

sed -ne '16224,16482w newfile' -e '16482q' file

lub korzystając z bash:

sed -n $'16224,16482w newfile\n16482q' file

1

Za pomocą ed:

ed -s infile <<<'16224,16482p'

-stłumi wyjście diagnostyczne; rzeczywiste polecenia znajdują się w ciągu tutaj. W szczególności 16224,16482puruchamia polecenie p(drukuj) w żądanym zakresie adresów linii.


0

Opcja -n w zaakceptowanych odpowiedziach działa. Oto inny sposób na wypadek, gdybyś był skłonny.

cat $filename | sed "${linenum}p;d";

Robi to:

  1. wstaw zawartość pliku (lub podaj tekst, jak chcesz).
  2. sed wybiera daną linię, drukuje ją
  3. d jest wymagane do usunięcia linii, w przeciwnym razie sed przyjmie, że wszystkie linie zostaną wydrukowane. tzn. bez litery d wszystkie wiersze zostaną wydrukowane przez wybraną linię dwukrotnie, ponieważ masz część $ {linenum} p z prośbą o wydrukowanie. Jestem prawie pewien, że -n zasadniczo robi to samo, co tutaj.

3
uwaga cat file | sedjest lepiej napisana jakosed file
— fedorqui „SO przestań krzywdzić”

Również to po prostu wypisuje linię, podczas gdy pytanie dotyczy ich zakresu.
— fedorqui „SO przestań krzywdzić”

0

Ponieważ mówimy o wydobywaniu wierszy tekstu z pliku tekstowego, dam specjalny przypadek, w którym chcesz wyodrębnić wszystkie wiersze pasujące do określonego wzorca.

myfile content:
=====================
line1 not needed
line2 also discarded
[Data]
first data line
second data line
=====================
sed -n '/Data/,$p' myfile

Wydrukuje linię [Data] i pozostałe. Jeśli chcesz tekst od linii 1 do wzoru, wpisz: sed -n '1, / Data / p' mój_plik. Ponadto, jeśli znasz dwa wzorce (lepiej bądź unikalny w tekście), zarówno początkową, jak i końcową linię zakresu można określić za pomocą dopasowań.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.