Come posso estrarre un intervallo predeterminato di righe da un file di testo su Unix?


532

Ho un dump SQL di ~ 23000 righe contenente diversi database di dati. Ho bisogno di estrarre una certa sezione di questo file (cioè i dati per un singolo database) e inserirlo in un nuovo file. Conosco sia il numero iniziale che quello finale dei dati che desidero.

Qualcuno conosce un comando Unix (o una serie di comandi) per estrarre tutte le linee da un file tra diciamo linea 16224 e 16482 e quindi reindirizzarle in un nuovo file?


Dal momento che si parla di file di grandi dimensioni, vi suggerisco di controllare commento stackoverflow.com/questions/83329/...
sancho.s ReinstateMonicaCellio

Risposte:


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

Dal manuale di sed :

p - Stampa lo spazio del motivo (sull'output standard). Questo comando viene solitamente utilizzato solo insieme all'opzione -n ​​della riga di comando.

n - Se la stampa automatica non è disabilitata, stampare lo spazio del motivo, quindi, a prescindere, sostituire lo spazio del motivo con la riga di input successiva. Se non vi sono più input, sed esce senza elaborare altri comandi.

q - Esci sedsenza elaborare altri comandi o input. Si noti che lo spazio del motivo corrente viene stampato se la stampa automatica non è disabilitata con l'opzione -n.

e

Gli indirizzi in uno script sed possono essere in una delle seguenti forme:

numero La specifica di un numero di riga corrisponderà solo a quella riga nell'input.

È possibile specificare un intervallo di indirizzi specificando due indirizzi separati da una virgola (,). Un intervallo di indirizzi corrisponde alle righe a partire da dove corrisponde il primo indirizzo e continua fino a quando il secondo indirizzo corrisponde (inclusivamente).


3
Ero curioso di sapere se questo modifica il file originale. Ho eseguito il backup per ogni evenienza e sembra che questo NON abbia modificato l'originale, come previsto.
Andy Groff,

@AndyGroff. Per modificare il file in atto, utilizzare il parametro "-i". Altrimenti non modificherà il file.
tuo

175
Se, come me, devi farlo su un file MOLTO grande, aiuta se aggiungi un comando quit sulla riga successiva. Allora lo è sed -n '16224,16482p;16483q' filename. Altrimenti sed continuerà a scansionare fino alla fine (o almeno la mia versione lo fa).
wds,

7
Le persone di @MilesRout sembrano chiedersi "perché il downvote?" abbastanza spesso, forse intendi "Non mi interessa" invece di "a nessuno importa"
Mark

1
@wds - Il tuo commento merita una risposta che sale in cima. Può fare la differenza tra giorno e notte.
sancho.s ReinstateMonicaCellio il

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

Dove 16224,16482 sono il numero della riga iniziale e il numero della riga finale, inclusi. Questo è 1-indicizzato. -nsopprime l'eco dell'input come output, che chiaramente non si desidera; i numeri indicano l'intervallo di righe su cui operare il seguente comando; il comando pstampa le righe pertinenti.


7
Su file di grandi dimensioni, il comando sopra continuerà a percorrere l'intero file dopo aver trovato l'intervallo desiderato. C'è un modo per far sed interrompere l'elaborazione del file una volta che l'intervallo è stato emesso?
Gary,

39
Ebbene, da qui la risposta , sembra che fermarsi alla fine del campo potrebbe essere realizzato con: sed -n '16224,16482p;16482q' orig-data-file > new-file.
Gary,

5
Perché dovresti mettere in uno spazio non necessario e poi citare? (Certo, fare problemi inutili e risolverli è l'essenza di metà dell'informatica, ma intendo a parte quel motivo ...)
Kaz,

92

Abbastanza semplice usando testa / coda:

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

usando sed:

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

usando awk:

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

1
La seconda e la terza opzione sono OK, ma la prima è più lenta di molte alternative perché utilizza 2 comandi dove 1 è sufficiente. Richiede anche il calcolo per ottenere l'argomento giusto tail.
Jonathan Leffler,

3
Vale la pena notare che per mantenere gli stessi numeri di riga della domanda, il comando sed dovrebbe essere sed -n 16224,16482p' in.sql >out.sqle il comando awk dovrebbe essereawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz,

3
Vale anche la pena sapere che nel caso del primo esempio head -16482 in.sql | tail -$((16482-16224)) >out.sqllascia il calcolo a bash
sibaz,

1
Il primo con testa e coda WAYYYY più veloce su file di grandi dimensioni rispetto alla versione sed, anche con l'opzione q aggiunta. testa-versione istantanea e versione sed I Ctrl-C dopo un minuto ... Grazie
Miyagi,

2
Potrebbe anche essere usato tail -n +16224per ridurre il calcolo
SOFe

35

È possibile utilizzare 'vi' e quindi il seguente comando:

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

In alternativa:

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

MODIFICA: - Solo per aggiungere una spiegazione, usi head -n 16482 per visualizzare le prime 16482 linee quindi usa tail -n 258 per ottenere le ultime 258 linee dal primo output.


2
E invece di vi potresti usare ex, ovvero vi meno le cose della console interattiva.
Tadeusz A. Kadłubowski il

1
Non hai bisogno del catcomando; headpuò leggere direttamente un file. Questo è più lento di molte alternative perché utilizza 2 (3 come mostrato) comandi dove 1 è sufficiente.
Jonathan Leffler,

1
@JonathanLeffler Ti sbagli di grosso. È incredibilmente veloce. Estraggo 200k linee, circa 1G, da un file 2G con 500k linee, in pochi secondi (senza cat). Altre soluzioni richiedono almeno alcuni minuti. Anche la variazione più veloce su GNU sembra essere tail -n +XXX filename | head XXX.
Antonis Christofides,

28

C'è un altro approccio con awk:

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

Se il file è enorme, può essere utile exitdopo aver letto l'ultima riga desiderata. In questo modo, non leggerà inutilmente le seguenti righe:

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

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

2
1+ per risparmiare tempo di esecuzione e risorse utilizzando print; exit. Grazie !
Bernie Reiter,

Leggera semplificazione del secondo esempio:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade,

È brillante, grazie @ RobinA.Meade! Ho modificato la tua idea nel post
fedorqui "SO smettere di danneggiare" il

17
perl -ne 'print if 16224..16482' file.txt > new_file.txt

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

dovrebbe fare il trucco. Il rovescio della medaglia di questo approccio è che devi fare l'aritmetica per determinare l'argomento per tail e per capire se vuoi che il 'Between' includa la linea di arrivo o meno.


4
Non hai bisogno del catcomando; headpuò leggere direttamente un file. Questo è più lento di molte alternative perché utilizza 2 (3 come mostrato) comandi dove 1 è sufficiente.
Jonathan Leffler,

@JonathanLeffler Questa risposta è la più facile da leggere e ricordare. Se ti importasse davvero delle prestazioni, non avresti usato una shell in primo luogo. È buona prassi lasciare che strumenti specifici si dedichino a un determinato compito. Inoltre, l '"aritmetica" può essere risolta usando | tail -$((16482 - 16224)).
Yeti,

6

In piedi sulle spalle di boxxar, mi piace questo:

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

per esempio

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

I $mezzi "ultima linea", in modo che il primo comando fa sedstampare tutte le linee che iniziano con la linea 16224e la seconda rende comando sedsmettere dopo la stampa linea 16428. (L'aggiunta 1di q-range nella soluzione di boxxar non sembra essere necessaria.)

Mi piace questa variante perché non è necessario specificare due volte il numero della riga finale. E ho misurato che l'utilizzo $non ha effetti dannosi sulle prestazioni.



3

Veloce e sporco:

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

Probabilmente non è il modo migliore per farlo, ma dovrebbe funzionare.

A proposito: 259 = 16482-16224 + 1.


Questo è più lento di molte alternative perché utilizza 2 comandi dove 1 è sufficiente.
Jonathan Leffler,

3

Ho scritto un programma Haskell chiamato splitter che fa esattamente questo: leggi il mio post sul blog di rilascio .

È possibile utilizzare il programma come segue:

$ cat somefile | splitter 16224-16482

E questo è tutto ciò che c'è da fare. Sarà necessario Haskell per installarlo. Appena:

$ cabal install splitter

E il gioco è fatto. Spero che questo programma ti sia utile.


Non splittersolo leggere dallo standard input? In un certo senso, non importa; il catcomando è superfluo se lo fa o no. Utilizzare splitter 16224-16482 < somefileo (se accetta argomenti relativi al nome del file) splitter 16224-16482 somefile.
Jonathan Leffler,

3

Anche noi possiamo farlo per controllare dalla riga di comando:

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

Per esempio:

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

6
Non è necessario il catcomando in nessuno di questi; sedè perfettamente in grado di leggere i file da solo oppure puoi reindirizzare l'input standard da un file.
Jonathan Leffler,

3

Usando il rubino:

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

2

Stavo per pubblicare il trucco testa / coda, ma in realtà probabilmente avrei appena lanciato emacs. ;-)

  1. esc- xgoto-line ret16224
  2. mark ( ctrl- space)
  3. esc- xgoto-line ret16482
  4. esc-w

apri il nuovo file di output, ctl-y save

Vediamo cosa sta succedendo.


4
Emacs non funziona molto bene su file molto grandi nella mia esperienza.
Greg Mattes,

Puoi eseguirlo come un'azione con script o è solo un'opzione interattiva?
Jonathan Leffler,

2

Io userei:

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

FNR contiene il numero record (riga) della riga letta dal file.


2

Volevo fare la stessa cosa da uno script usando una variabile e l'ho raggiunto mettendo virgolette attorno alla variabile $ per separare il nome della variabile da p:

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

Volevo dividere un elenco in cartelle separate e ho trovato la domanda iniziale e ho risposto a un passaggio utile. (comando diviso non è un'opzione sul vecchio sistema operativo a cui devo codice di porta).


1

Ho scritto un piccolo script bash che puoi eseguire dalla tua riga di comando, purché aggiorni il PERCORSO per includerne la directory (o puoi inserirlo in una directory che è già contenuta nel PERCORSO).

Uso: $ pinch nome-file della linea di fine della linea di inizio

#!/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
Questo è più lento di molte alternative perché utilizza 2 comandi dove 1 è sufficiente. In effetti, legge il file due volte a causa del wccomando, che spreca la larghezza di banda del disco, specialmente sui file gigabyte. In tutti i modi, questo è ben documentato, ma è anche ingegneristico eccessivo.
Jonathan Leffler,

1

Questo potrebbe funzionare per te (GNU sed):

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

o approfittando di bash:

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

1

Utilizzando ed:

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

-ssopprime l'output diagnostico; i comandi effettivi sono in una stringa qui. In particolare, 16224,16482pesegue il pcomando (stampa) sull'intervallo di indirizzi di riga desiderato.


0

Il -n nelle risposte accettate funziona. Ecco un altro modo nel caso tu sia propenso.

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

Questo fa quanto segue:

  1. inserisci il contenuto di un file (o inserisci il testo nel modo che preferisci).
  2. sed seleziona la riga indicata, la stampa
  3. d è necessario per eliminare le righe, altrimenti sed supporrà che tutte le righe vengano stampate. cioè, senza la d, tutte le righe verranno stampate dalla riga selezionata stampata due volte perché hai la parte $ {linenum} p che ti chiede di essere stampata. Sono abbastanza sicuro che -n stia sostanzialmente facendo la stessa cosa della d qui.

3
nota cat file | sedè meglio scritta comesed file
fedorqui 'SO smettere di danneggiare'

Anche questo stampa solo una linea, mentre la domanda riguarda una gamma di essi.
fedorqui "SO smette di danneggiare" il

0

Dato che stiamo parlando dell'estrazione di righe di testo da un file di testo, fornirò un caso speciale in cui si desidera estrarre tutte le righe che corrispondono a un determinato modello.

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

Stampa la riga [Dati] e il resto. Se si desidera il testo dalla riga 1 al modello, digitare: sed -n '1, / Data / p' myfile. Inoltre, se conosci due pattern (meglio essere univoci nel tuo testo), sia la linea iniziale che quella finale dell'intervallo possono essere specificate con corrispondenze.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.