Comando unix rápido para exibir linhas específicas no meio de um arquivo?

206

Tentando depurar um problema em um servidor e meu único arquivo de log é um arquivo de 20 GB (sem timestamps! Por que as pessoas usam System.out.println()como log? Na produção ?!)

Usando grep, encontrei uma área do arquivo que gostaria de dar uma olhada na linha 347340107.

Além de fazer algo como

head -<$LINENUM + 10> filename | tail -20 

... o que exigiria heada leitura das primeiras 347 milhões de linhas do arquivo de log, existe um comando rápido e fácil que despejaria as linhas 347340100 - 347340200 (por exemplo) no console?

update Esqueci totalmente que o grep pode imprimir o contexto em torno de uma partida ... isso funciona bem. Obrigado!

matt b
fonte
Eu imagino que o grep tem que pesquisar em todo o arquivo, deve haver uma maneira menos intensa de CPU para fazer isso.
ojblass

Respostas:

69

com o GNU-grep você poderia dizer

grep --context = 10 ...

fonte
7
Ou, mais especificamente, 10 linhas antes: grep -B 10 ... ou 10 linhas depois: -A grep 10 ...
Menino Baukema
17
Este comando não está funcionando, abaixo de sed -n '<start>, <end> p' está funcionando
Basav
5
Na verdade, isso não é o que você deseja, porque processará o arquivo inteiro, mesmo que a correspondência esteja na parte superior. Neste ponto, uma combinação de cabeça / cauda ou cauda / cabeça é muito mais eficaz.
Sklivvz
3
Isso não satisfaz a pergunta feita, pois não oferece uma maneira de gerar uma linha específica , conforme solicitado.
Chris Rasys
1
Na verdade, não foi isso que foi perguntado. @matt b, por que você não aceita esta resposta?
User1271772 26/01
390

Encontrei duas outras soluções, se você souber o número da linha, mas nada mais (não é possível grep):

Supondo que você precise das linhas 20 a 40,

sed -n '20,40p;41q' file_name

ou

awk 'FNR>=20 && FNR<=40' file_name
Sklivvz
fonte
6
+1: Embora você queira sair após a impressão. Pode oferecer alguns benefícios de desempenho se o arquivo for realmente grande.
Jaypal singh
awk 'NR> = 20 && NR <= 40' file_name
Sudipta Basak
2
sed -n '20, 40p; 41q 'file_name para sair então.
Snigdha Batra
1
especificamente, esses são os números das linhas inicial e final. Se você estiver em um arquivo maior, será '12345678,12345699p'
Code Abominator
1
Além do comentário do @ CodeAbominator, 41qinstrua o sed a sair de linha 41.
Brice
116
# print line number 52
sed -n '52p' # method 1
sed '52!d' # method 2
sed '52q;d' # method 3,  efficient on large files 

método 3 eficiente em arquivos grandes

maneira mais rápida de exibir linhas específicas

CMI
fonte
Estou tentando descobrir como adaptar o método 3 para usar um intervalo em vez de uma única linha, mas temo que meu sed-foo não esteja preparado para a tarefa.
Xiong Chiamiov 07/07
9
@XiongChiamiov Que tal sed -n '1.500p; 501q' para impressão 1-500?
Sam
3
A razão as duas primeiras linhas / métodos são menos eficientes, é que eles continuar a processar todas as linhas após a linha 52, até o fim, enquanto # 3 paradas após a impressão Linha 52.
flow2k
1
Essa resposta se beneficiaria de explicar o que todos os argumentos fazem.
Bram Vanroy 03/02
25

Não, não há, os arquivos não são endereçáveis ​​por linha.

Não existe uma maneira constante de encontrar o início da linha n em um arquivo de texto. Você deve transmitir pelo arquivo e contar novas linhas.

Use a ferramenta mais simples / rápida para executar o trabalho. Para mim, usar headfaz muito mais sentido do que grep, uma vez que o último é muito mais complicado. Não estou dizendo " grepé lento", realmente não é, mas ficaria surpreso se for mais rápido do que headneste caso. Isso seria um bug head, basicamente.

descontrair
fonte
2
A menos que as linhas tenham largura fixa em bytes, você não sabe para onde mover o ponteiro do arquivo sem contar os novos caracteres de linha desde o início do arquivo.
Joseph Lust
Isso não fornece uma resposta para a pergunta. Para criticar ou solicitar esclarecimentos a um autor, deixe um comentário abaixo da postagem.
Exhuma 22/05
@exhuma Você está certo. Eu reescrevi. Sete anos atrás, eu fiquei irritada. :)
descontraia
20

A respeito:

tail -n +347340107 filename | head -n 100

Não testei, mas acho que funcionaria.

itsmatt
fonte
Não, normalmente a cauda tem um limite de 256 últimos kilobytes ou similar, dependendo da versão e do sistema operacional.
Antti Rytsölä
💪 yessire miller
dctremblay 19/10/19
13

Eu prefiro apenas entrar lesse

  • digitando 50%para ir até a metade do arquivo,
  • 43210G para ir para a linha 43210
  • :43210 para fazer o mesmo

e coisas assim.

Ainda melhor: pressione vpara começar a editar (no vim, é claro!), Naquele local. Agora, observe que vimpossui as mesmas ligações de teclas!

ver
fonte
12

Eu primeiro dividi o arquivo em alguns menores como este

$ split --lines=50000 /path/to/large/file /path/to/output/file/prefix

e depois grep nos arquivos resultantes.

Luka Marinko
fonte
concordado, interrompa esse log e crie um trabalho cron para fazer isso corretamente. use logrotate ou algo semelhante para impedir que fiquem tão grandes.
Tanj
9

Você pode usar o excomando, um editor Unix padrão (parte do Vim agora), por exemplo

  • exibir uma única linha (por exemplo, a segunda):

    ex +2p -scq file.txt

    sintaxe sed correspondente: sed -n '2p' file.txt

  • intervalo de linhas (por exemplo, 2-5 linhas):

    ex +2,5p -scq file.txt

    sintaxe sed: sed -n '2,5p' file.txt

  • da linha especificada até o final (por exemplo, do 5º ao final do arquivo):

    ex +5,p -scq file.txt

    sintaxe sed: sed -n '2,$p' file.txt

  • vários intervalos de linhas (por exemplo, 2-4 e 6-8 linhas):

    ex +2,4p +6,8p -scq file.txt

    sintaxe sed: sed -n '2,4p;6,8p' file.txt

Os comandos acima podem ser testados com o seguinte arquivo de teste:

seq 1 20 > file.txt

Explicação:

  • +ou -cseguido pelo comando - execute o comando (vi / vim) após a leitura do arquivo,
  • -s - modo silencioso, também usa o terminal atual como saída padrão,
  • qseguido por -cé o comando para sair do editor (adicione !para fazer sair da força, por exemplo -scq!).
kenorb
fonte
7

Se o seu número de linha for 100 para ler

head -100 filename | tail -1
Roopa
fonte
6

Obter ack

Instalação do Ubuntu / Debian:

$ sudo apt-get install ack-grep

Então corra:

$ ack --lines=$START-$END filename

Exemplo:

$ ack --lines=10-20 filename

De $ man ack:

--lines=NUM
    Only print line NUM of each file. Multiple lines can be given with multiple --lines options or as a comma separated list (--lines=3,5,7). --lines=4-7 also works. 
    The lines are always output in ascending order, no matter the order given on the command line.
Odeyin
fonte
1
Isso, para mim, parece o comando com a sintaxe mais intuitiva de todas as respostas aqui.
Nzn 26/04/19
Da versão 2.999_06 em 10 de janeiro de 2019, o --linesparâmetro foi removido.
burny
4

O sed precisará ler os dados também para contar as linhas. A única maneira possível de um atalho seria o contexto / ordem no arquivo para operar. Por exemplo, se houvesse linhas de log anexadas com uma largura / hora / data fixas etc., você poderia usar o utilitário look unix para pesquisar binariamente nos arquivos por datas / horas específicas

pixelbeat
fonte
4

Usar

x=`cat -n <file> | grep <match> | awk '{print $1}'`

Aqui você obterá o número da linha onde a partida ocorreu.

Agora você pode usar o seguinte comando para imprimir 100 linhas

awk -v var="$x" 'NR>=var && NR<=var+100{print}' <file>

ou você pode usar "sed" também

sed -n "${x},${x+100}p" <file>
Ramana Reddy
fonte
Se você tiver mais de uma correspondência, use: "awk 'NR == 1 {print $ 1}" para a primeira correspondência e assim por diante
Ramana Reddy
2

Com a sed -e '1,N d; M q'impressão das linhas N + 1 a M. Isso provavelmente é um pouco melhor, grep -Cpois não tenta corresponder as linhas a um padrão.

Mweerden
fonte
-eé opcional aqui.
Flow2k 28/05/19
2

Com base na resposta do Sklivvz, aqui está uma boa função que você pode colocar em um .bash_aliasesarquivo. É eficiente em arquivos enormes ao imprimir coisas pela frente do arquivo.

function middle()
{
    startidx=$1
    len=$2
    endidx=$(($startidx+$len))
    filename=$3

    awk "FNR>=${startidx} && FNR<=${endidx} { print NR\" \"\$0 }; FNR>${endidx} { print \"END HERE\"; exit }" $filename
}
Keithel
fonte
1

Para exibir uma linha de a <textfile>por sua <line#>, faça o seguinte:

perl -wne 'print if $. == <line#>' <textfile>

Se você deseja uma maneira mais poderosa de mostrar um intervalo de linhas com expressões regulares - não direi por que o grep é uma má idéia para fazer isso, deve ser bastante óbvio - essa expressão simples mostrará seu intervalo em um passe único que é o que você deseja ao lidar com arquivos de texto de ~ 20 GB:

perl -wne 'print if m/<regex1>/ .. m/<regex2>/' <filename>

(dica: se o seu regex tiver /, use algo como isso m!<regex>!)

Isso imprimiria <filename>começando com a linha que corresponde <regex1>até (e incluindo) a linha que corresponde <regex2>.

Não é preciso um assistente para ver como alguns ajustes podem torná-lo ainda mais poderoso.

Última coisa: o perl, por ser uma linguagem madura, possui muitas melhorias ocultas para favorecer a velocidade e o desempenho. Com isso em mente, torna a escolha óbvia para essa operação, uma vez que foi originalmente desenvolvida para lidar com grandes arquivos de log, texto, bancos de dados etc.

osirisgothra
fonte
realmente, não me parece assim, desde quando executar um comando perl mais complicado do que dizer, executar mais de 2 programas juntos (mais abaixo na página) e acho que você está dizendo porque digitei mais uma explicação que você obrigados a ler, uma vez que existem igualmente complexo (ou mais) para baixo a página que não se soprado para fora da água ... sheesh
osirisgothra
Observe que o usuário solicitou um intervalo de linhas - seu exemplo pode ser trivialmente adaptado.
Sklivvz
0

Você pode tentar este comando:

egrep -n "*" <filename> | egrep "<line number>"
Fritz Dodoo
fonte
0

Fácil com perl! Se você deseja obter as linhas 1, 3 e 5 de um arquivo, diga / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,3,5]){print}}' < /etc/passwd
dagelf
fonte
1
Você diz que é fácil com o awk, mas você fez em perl?
Prisoner 13
0

Surpreende-me apenas uma outra resposta (de Ramana Reddy) sugerida para adicionar números de linha à saída. A seguir, procura o número da linha necessária e pinta a saída.

file=FILE
lineno=LINENO
wb="107"; bf="30;1"; rb="101"; yb="103"
cat -n ${file} | { GREP_COLORS="se=${wb};${bf}:cx=${wb};${bf}:ms=${rb};${bf}:sl=${yb};${bf}" grep --color -C 10 "^[[:space:]]\\+${lineno}[[:space:]]"; }
enguia ghEEz
fonte
As respostas com código tendem a ser sinalizadas para exclusão. Você poderia adicionar algum comentário sobre como isso resolve o problema?
Graham