Como posso extrair um intervalo predeterminado de linhas de um arquivo de texto no Unix?


531

Eu tenho um despejo de SQL ~ 23000 linhas contendo vários bancos de dados no valor de dados. Eu preciso extrair uma certa seção deste arquivo (ou seja, os dados para um único banco de dados) e colocá-lo em um novo arquivo. Conheço os números das linhas inicial e final dos dados que desejo.

Alguém conhece um comando Unix (ou série de comandos) para extrair todas as linhas de um arquivo entre as linhas 16224 e 16482 e, em seguida, redirecioná-las para um novo arquivo?


Como você menciona arquivos grandes, sugiro verificar o comentário stackoverflow.com/questions/83329/…
sancho.s ReinstateMonicaCellio

Respostas:


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

No manual sed :

p - Imprima o espaço do padrão (na saída padrão). Esse comando geralmente é usado apenas em conjunto com a opção de linha de comando -n.

n - Se a impressão automática não estiver desativada, imprima o espaço do padrão e, independentemente, substitua o espaço do padrão pela próxima linha de entrada. Se não houver mais entrada, o sed sai sem processar mais nenhum comando.

q - Saia sedsem processar mais nenhum comando ou entrada. Observe que o espaço padrão atual é impresso se a impressão automática não estiver desativada com a opção -n.

e

Os endereços em um script sed podem estar em qualquer uma das seguintes formas:

número A especificação de um número de linha corresponderá apenas a essa linha na entrada.

Um intervalo de endereços pode ser especificado especificando dois endereços separados por vírgula (,). Um intervalo de endereços corresponde a linhas começando de onde o primeiro endereço corresponde e continua até o segundo endereço corresponder (inclusive).


3
Fiquei curioso para saber se isso modifica o arquivo original. Eu fiz o backup apenas por precaução e parece que isso NÃO modificou o original, conforme o esperado.
Andy Groff

@AndyGroff. Para modificar o arquivo no local, use o parâmetro "-i". Caso contrário, ele não modificará o arquivo.
youri

175
Se, como eu, você precisar fazer isso em um arquivo MUITO grande, ajudará se você adicionar um comando quit na próxima linha. Então é isso sed -n '16224,16482p;16483q' filename. Caso contrário, o sed continuará digitalizando até o fim (ou pelo menos minha versão).
Wds #

7
As pessoas do @MilesRout parecem perguntar "por que o voto negativo?" muitas vezes, talvez você quer dizer "eu não me importo" em vez de "ninguém se importa"
Mark

1
@wds - Seu comentário merece uma resposta que sobe ao topo. Pode fazer a diferença entre dia e noite.
Sancho.s ReinstateMonicaCellio

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

Onde 16224,16482 são o número da linha inicial e o número da linha final, inclusive. Isso é indexado em 1. -nsuprime o eco da entrada como saída, o que você claramente não deseja; os números indicam o intervalo de linhas para que o seguinte comando opere; o comando pimprime as linhas relevantes.


7
Em arquivos grandes, o comando acima continuará percorrendo o arquivo inteiro depois que o intervalo desejado for encontrado. Existe uma maneira de o sed parar de processar o arquivo após a saída do intervalo?
Gary

39
Bem, desde a resposta aqui , parece que parar no final do intervalo poderia ser conseguido com: sed -n '16224,16482p;16482q' orig-data-file > new-file.
Gary

5
Por que você colocaria um espaço desnecessário e depois teria que citar? (Claro, fazendo problemas desnecessários e resolvê-los é a essência da metade da informática, mas eu quero dizer ao lado esse motivo ...)
Kaz

92

Muito simples usando cabeça / cauda:

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
A segunda e a terceira opções estão OK, mas a primeira é mais lenta que muitas alternativas porque usa 2 comandos, onde 1 é suficiente. Também requer computação para obter o argumento correto tail.
Jonathan Leffler

3
Vale notar que para manter os mesmos números de linha como a questão, o comando sed deve ser sed -n 16224,16482p' in.sql >out.sqleo comando awk deve serawk 'NR>=16224&&NR<=16482' in.sql > out.sql
sibaz

3
Também vale a pena saber que, no caso do primeiro exemplo, head -16482 in.sql | tail -$((16482-16224)) >out.sqldeixa o cálculo para bash
sibaz 26/02

1
O primeiro com cabeça e cauda WAYYYY mais rápido em arquivos grandes que a versão sed, mesmo com a opção q adicionada. head-versão instantânea e versão sed I Ctrl-C depois de um minuto ... Obrigado
Miyagi

2
Também pode usar tail -n +16224para reduzir a computação
sofe

35

Você pode usar 'vi' e, em seguida, o seguinte comando:

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

Alternativamente:

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

EDIT: - Apenas para adicionar uma explicação, use o comando -n 16482 para exibir as primeiras 16482 linhas e, em seguida, use o tail -n 258 para obter as últimas 258 linhas da primeira saída.


2
E, em vez do vi, você pode usar ex, ou seja, menos o material do console interativo.
Tadeusz A. Kadłubowski 25/03

1
Você não precisa do catcomando; headpode ler um arquivo diretamente. Isso é mais lento que muitas alternativas porque usa 2 (3 como mostrado) comandos em que 1 é suficiente.
Jonathan Leffler

1
@ JonathanLeffler Você está completamente errado. É incrivelmente rápido. Extraio 200k linhas, aproximadamente 1G, de um arquivo 2G com 500k linhas, em alguns segundos (sem o cat). Outras soluções precisam de pelo menos alguns minutos. Também parece ser a variação mais rápida no GNU tail -n +XXX filename | head XXX.
Antonis Christofides

28

Há outra abordagem com awk:

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

Se o arquivo for grande, pode ser bom exitdepois de ler a última linha desejada. Dessa forma, ele não lerá as seguintes linhas desnecessariamente:

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

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

2
1+ para economizar tempo de execução e recursos usando print; exit. Obrigado !
Bernie Reiter

Pequena simplificação do segundo exemplo:awk 'NR==16224, NR==16482; NR==16482 {exit}' file
Robin A. Meade

Isso é brilhante, obrigado @ RobinA.Meade! Eu editei sua ideia no post
fedorqui 'SO stop harming'

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

deve fazer o truque. A desvantagem dessa abordagem é que você precisa fazer a aritmética para determinar o argumento da cauda e explicar se deseja que o 'entre' inclua a linha final ou não.


4
Você não precisa do catcomando; headpode ler um arquivo diretamente. Isso é mais lento que muitas alternativas porque usa 2 (3 como mostrado) comandos em que 1 é suficiente.
Jonathan Leffler

@ JonathanLeffler Esta resposta é a mais fácil de ler e lembrar. Se você realmente se importasse com o desempenho, não usaria um shell em primeiro lugar. É uma boa prática permitir que ferramentas específicas se dediquem a uma determinada tarefa. Além disso, a "aritmética" pode ser resolvida usando | tail -$((16482 - 16224)).
Yeti

6

De pé sobre os ombros de boxxar, eu gosto deste:

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

por exemplo

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

Como $significa "última linha", o primeiro comando faz com que sedtodas as linhas sejam impressas começando com a linha 16224e o segundo comando é sedencerrado após a impressão da linha 16428. (Adicionando 1para oq Parece não ser necessário intervalo na solução de boxxar.)

Eu gosto dessa variante porque não preciso especificar o número da linha final duas vezes. E eu medi que o uso $não tem efeitos prejudiciais no desempenho.



3

Rapido e sujo:

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

Provavelmente não é a melhor maneira de fazê-lo, mas deve funcionar.

BTW: 259 = 16482-16224 + 1.


Isso é mais lento do que muitas alternativas, porque usa 2 comandos, onde 1 é suficiente.
Jonathan Leffler

3

Eu escrevi um programa Haskell chamado splitter que faz exatamente isso: leia a publicação do meu blog de lançamento .

Você pode usar o programa da seguinte maneira:

$ cat somefile | splitter 16224-16482

E isso é tudo o que há para isso. Você precisará do Haskell para instalá-lo. Somente:

$ cabal install splitter

E você terminou. Espero que você ache este programa útil.


Será que splittersomente leitura da entrada padrão? Em certo sentido, isso não importa; o catcomando é supérfluo, independentemente de existir ou não. Use splitter 16224-16482 < somefileou (se houver argumentos de nome de arquivo) splitter 16224-16482 somefile.
Jonathan Leffler

3

Mesmo nós podemos fazer isso para verificar na linha de comando:

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

Por exemplo:

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

6
Você não precisa do catcomando em nenhum deles; sedé perfeitamente capaz de ler arquivos por conta própria ou você pode redirecionar a entrada padrão de um arquivo.
Jonathan Leffler

3

Usando ruby:

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

2

Eu estava prestes a postar o truque de cabeça / cauda, ​​mas na verdade eu provavelmente iria iniciar o emacs. ;-)

  1. esc- xlinha goto ret16224
  2. marca ( ctrl- space)
  3. esc- xlinha goto ret16482
  4. esc-w

abra o novo arquivo de saída, salve ctl-y

Vamos ver o que está acontecendo.


4
O Emacs não funciona muito bem em arquivos muito grandes na minha experiência.
Greg Mattes

Você pode executar isso como uma ação com script ou é apenas uma opção interativa?
Jonathan Leffler

2

Eu usaria:

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

FNR contém o número do registro (linha) da linha que está sendo lida no arquivo.


2

Eu queria fazer a mesma coisa a partir de um script usando uma variável e consegui-o colocando aspas em torno da variável $ para separar o nome da variável do p:

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

Eu queria dividir uma lista em pastas separadas e encontrei a pergunta inicial e respondi a uma etapa útil. (comando split não é uma opção no sistema operacional antigo para o qual tenho que portar o código).


1

Eu escrevi um pequeno script bash que você pode executar a partir da linha de comando, desde que atualize seu PATH para incluir seu diretório (ou você pode colocá-lo em um diretório que já esteja contido no PATH).

Uso: $ pinch filename start-line end-line

#!/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
Isso é mais lento do que muitas alternativas, porque usa 2 comandos, onde 1 é suficiente. De fato, ele lê o arquivo duas vezes por causa do wccomando, que desperdiça largura de banda do disco, especialmente em arquivos de gigabyte. De todas as formas, isso está bem documentado, mas também é um exagero de engenharia.
Jonathan Leffler

1

Isso pode funcionar para você (GNU sed):

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

ou aproveitando o bash:

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

1

Usando ed:

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

-ssuprime a saída de diagnóstico; os comandos reais estão em uma string here. Especificamente, 16224,16482pexecuta o pcomando (impressão) no intervalo de endereços de linha desejado.


0

O -n nas respostas de aceitação funciona. Aqui está outra maneira, caso você esteja inclinado.

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

Isso faz o seguinte:

  1. canalize o conteúdo de um arquivo (ou alimente o texto da maneira que desejar).
  2. sed seleciona a linha especificada, imprime-a
  3. d é necessário para excluir linhas, caso contrário, o sed assumirá que todas as linhas serão impressas. ou seja, sem o d, todas as linhas impressas pela linha selecionada serão impressas duas vezes, porque você tem a parte $ {clothum} p solicitando a impressão. Tenho certeza de que o -n está basicamente fazendo a mesma coisa que o d aqui.

3
nota cat file | sedé melhor escrita comosed file
fedorqui 'Então, pare de prejudicar'

Além disso, isso apenas imprime uma linha, enquanto a pergunta é sobre uma variedade deles.
fedorqui 'Então, pare de prejudicar'

0

Como estamos falando de extrair linhas de texto de um arquivo de texto, darei um caso especial em que você deseja extrair todas as linhas que correspondem a um determinado padrão.

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

Irá imprimir a linha [Dados] e o restante. Se você quiser o texto da linha1 para o padrão, digite: sed -n '1, / Data / p' myfile. Além disso, se você conhece dois padrões (melhor ser exclusivo em seu texto), tanto a linha inicial quanto a final do intervalo podem ser especificadas com correspondências.

sed -n '/BEGIN_MARK/,/END_MARK/p' myfile
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.