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?

Adam J. Forster
źródło
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).

boxxar
źródło
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.

JXG
źródło
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
manveru
źródło
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.

Mark Janssen
źródło
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
fedorqui „SO przestań szkodzić”
źródło
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ć”
17
perl -ne 'print if 16224..16482' file.txt > new_file.txt
mmaibaum
źródło
9
 # print section of file based on line numbers
 sed -n '16224 ,16482p'               # method 1
 sed '16224,16482!d'                 # method 2
Cetra
źródło
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.

JP Lodine
źródło
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ść.

Tilman Vogel
źródło
5

sed -n '16224,16482p' < dump.sql

cubex
źródło
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.

jan.vdbergh
źródło
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.

Robert Massaioli
źródło
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
Chinmoy Padhi
źródło
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
Carl Blakeley
źródło
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.

sammyo
źródło
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.

Paddy3118
źródło
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).

Kevin
źródło
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
DrNerdfighter
źródło
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
potong
źródło
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.

Benjamin W.
źródło
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.
ThinkBonobo
źródło
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
Kemin Zhou
źródło