Existe uma maneira "canônica" de fazer isso? Eu tenho usado o head -n | tail -1
que faz o truque, mas eu queria saber se existe uma ferramenta Bash que extrai especificamente uma linha (ou um intervalo de linhas) de um arquivo.
Por "canônico", quero dizer um programa cuja função principal é fazer isso.
awk
esed
e eu tenho certeza que alguém pode vir até com um one-liner Perl ou menos assim;)head | tail
solução está abaixo do ideal. Outras soluções quase ótimas foram sugeridas.head | tail
solução não funciona, se você consultar uma linha que não existe na entrada: ela imprimirá a última linha.Respostas:
head
e cano comtail
será lento para um arquivo enorme. Eu sugeririased
assim:Onde
NUM
está o número da linha que você deseja imprimir; por exemplo,sed '10q;d' file
para imprimir a 10ª linha defile
.Explicação:
NUMq
será encerrado imediatamente quando o número da linha forNUM
.d
excluirá a linha em vez de imprimi-la; isso é inibido na última linha porqueq
faz com que o restante do script seja ignorado ao sair.Se você possui
NUM
uma variável, convém usar aspas duplas em vez de simples:fonte
sed -n 'NUMp'
esed 'NUM!d'
propostas abaixo.tail -n+NUM file | head -n1
é provável que seja tão rápido ou mais rápido. Pelo menos, foi (significativamente) mais rápido no meu sistema quando tentei, com NUM sendo 250000 em um arquivo com meio milhão de linhas. YMMV, mas realmente não vejo por que isso aconteceria.cat
é realmente mais rápido (quase o dobro da velocidade), mas apenas se o arquivo ainda não tiver sido armazenado em cache . Depois que o arquivo é armazenado em cache , o uso direto do argumento filename é mais rápido (cerca de 1/3 mais rápido), enquanto ocat
desempenho permanece o mesmo. Curiosamente, no OS X 10.9.3 nada disso parece fazer diferença:cat
/ nãocat
, arquivo em cache ou não. @anubhava: o prazer é meu.sed 'NUMq
produzirá os primeirosNUM
arquivos e;d
excluirá tudo, exceto a última linha.imprimirá a segunda linha
2011th line
linha 10 até linha 33
1ª e 3ª linha
e assim por diante...
Para adicionar linhas com o sed, você pode verificar isso:
sed: insere uma linha em uma determinada posição
fonte
<
neste caso não é necessário. Simplesmente, é minha preferência usar redirecionamentos, porque eu costumava usar redirecionamentos comosed -n '100p' < <(some_command)
- então, sintaxe universal :). Ele não é menos eficaz, porque o redirecionamento é feito com casca quando se bifurcar-se, então ... é apenas uma preferência ... (e sim, é um personagem mais tempo) :)head
/tail
não resolve osed -n '1p;3p'
cenário - aka imprimir mais linhas não adjacentes ...Eu tenho uma situação única em que posso comparar as soluções propostas nesta página e, portanto, estou escrevendo esta resposta como uma consolidação das soluções propostas, com tempos de execução incluídos para cada um.
Configuração
Eu tenho um arquivo de dados de texto ASCII de 3.261 gigabytes com um par de valores-chave por linha. O arquivo contém 3,339,550,320 linhas no total e desafia a abertura em qualquer editor que eu tenha tentado, incluindo o meu go-to Vim. Preciso agrupar esse arquivo para investigar alguns dos valores que descobri que começam apenas na linha ~ 500.000.000.
Porque o arquivo tem tantas linhas:
Meu melhor cenário é uma solução que extrai apenas uma única linha do arquivo sem ler nenhuma das outras linhas do arquivo, mas não consigo pensar em como fazer isso no Bash.
Para os fins de minha sanidade mental, não vou tentar ler as 500.000.000 linhas completas necessárias para o meu próprio problema. Em vez disso, tentarei extrair a linha 50.000.000 de 3.339.550.320 (o que significa que a leitura do arquivo completo demorará 60 vezes mais que o necessário).
Eu usarei o
time
built-in para comparar cada comando.Linha de base
Primeiro vamos ver como a
head
tail
solução:A linha de base da linha 50 milhões é 00: 01: 15.321, se eu tivesse ido direto para a linha 500 milhões, provavelmente seria ~ 12,5 minutos.
cortar
Eu duvido disso, mas vale a pena tentar:
Este levou 00: 05: 12.156 para ser executado, o que é muito mais lento que a linha de base! Não tenho certeza se ele leu o arquivo inteiro ou apenas 50 milhões antes da parada, mas independentemente isso não parece uma solução viável para o problema.
AWK
Eu só executei a solução com o
exit
porque não iria esperar a execução do arquivo completo:Esse código foi executado em 00: 01: 16.583, que é apenas ~ 1 segundo mais lento, mas ainda não é uma melhoria na linha de base. Nesse ritmo, se o comando exit tivesse sido excluído, provavelmente levaria cerca de ~ 76 minutos para ler o arquivo inteiro!
Perl
Também executei a solução Perl existente:
Esse código foi executado em 00: 01: 13.146, que é ~ 2 segundos mais rápido que a linha de base. Se eu executasse o total de 500.000.000, provavelmente levaria ~ 12 minutos.
sed
A melhor resposta no quadro, aqui está o meu resultado:
Esse código foi executado em 00: 01: 12.705, que é 3 segundos mais rápido que a linha de base e ~ 0,4 segundos mais rápido que Perl. Se eu o executasse em 500.000.000 de linhas, provavelmente levaria cerca de 12 minutos.
mapfile
Tenho bash 3.1 e, portanto, não é possível testar a solução mapfile.
Conclusão
Parece que, na maioria das vezes, é difícil melhorar a
head
tail
solução. Na melhor das hipóteses, ased
solução fornece um aumento de ~ 3% na eficiência.(porcentagens calculadas com a fórmula
% = (runtime/baseline - 1) * 100
)Linha 50.000.000
sed
perl
head|tail
awk
cut
Linha 500.000.000
sed
perl
head|tail
awk
cut
Linha 3.338.559.320
sed
perl
head|tail
awk
cut
fonte
Com
awk
isso é bem rápido:Quando isso for verdade, o comportamento padrão
awk
é realizada:{print $0}
.Versões alternativas
Se o seu arquivo for enorme, é melhor
exit
depois de ler a linha necessária. Dessa forma, você economiza tempo da CPU. Veja a comparação do tempo no final da resposta .Se você deseja fornecer o número da linha de uma variável bash, pode usar:
Veja quanto tempo é economizado usando
exit
, especialmente se a linha estiver na primeira parte do arquivo:Portanto, a diferença é 0.198s vs 1.303s, cerca de 6x vezes mais rápido.
fonte
awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3
. Com o GNU awk, isso pode ser acelerado usandoawk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3
.FS=RS
evitar a divisão de campos?FS=RS
não evita a divisão de campos, mas apenas analisa os $ 0 e atribui apenas um campo porque não háRS
in$0
FS=RS
e não vi diferença nos horários. Que tal eu fazer uma pergunta sobre isso para poder expandir? Obrigado!De acordo com meus testes, em termos de desempenho e legibilidade, minha recomendação é:
tail -n+N | head -1
N
é o número da linha que você deseja. Por exemplo,tail -n+7 input.txt | head -1
imprimirá a 7ª linha do arquivo.tail -n+N
imprimirá tudo a partir da linhaN
ehead -1
fará com que pare após uma linha.A alternativa
head -N | tail -1
é talvez um pouco mais legível. Por exemplo, isso imprimirá a 7ª linha:head -7 input.txt | tail -1
Quando se trata de desempenho, não há muita diferença para tamanhos menores, mas será superado pelo
tail | head
(de cima) quando os arquivos ficarem enormes.O mais votado
sed 'NUMq;d'
é interessante saber, mas eu diria que ele será entendido por menos pessoas fora da caixa do que a solução cabeça / cauda e também é mais lento que a cauda / cabeça.Nos meus testes, ambas as versões caudas / cabeças tiveram
sed 'NUMq;d'
um desempenho consistente. Isso está alinhado com os outros benchmarks que foram publicados. É difícil encontrar um caso em que caudas / cabeças fossem realmente ruins. Também não é de surpreender, pois são operações que você esperaria que fossem fortemente otimizadas em um sistema Unix moderno.Para ter uma idéia das diferenças de desempenho, é o número que recebo de um arquivo enorme (9.3G):
tail -n+N | head -1
: 3,7 seghead -N | tail -1
: 4.6 segsed Nq;d
: 18,8 segOs resultados podem diferir, mas o desempenho
head | tail
etail | head
é, em geral, comparável para entradas menores esed
é sempre mais lento por um factor significativo (cerca de 5x mais ou menos).Para reproduzir meu benchmark, você pode tentar o seguinte, mas saiba que ele criará um arquivo 9.3G no diretório de trabalho atual:
Aqui está a saída de uma corrida na minha máquina (ThinkPad X1 Carbon com um SSD e 16G de memória). Presumo que na execução final tudo virá do cache, não do disco:
fonte
head | tail
vstail | head
? Ou depende de qual linha está sendo impressa (início do arquivo versus final do arquivo)?head -5 | tail -1
vstail -n+5 | head -1
. Na verdade, encontrei outra resposta que fez uma comparação de teste etail | head
que foi mais rápida. stackoverflow.com/a/48189289Uau, todas as possibilidades!
Tente o seguinte:
ou um deles, dependendo da sua versão do Awk:
( Talvez você precise tentar o comando
nawk
ougawk
).Existe uma ferramenta que apenas imprime essa linha específica? Não é uma das ferramentas padrão. No entanto,
sed
é provavelmente o mais próximo e mais simples de usar.fonte
Scripts de uma linha úteis para sed
fonte
Esta questão está sendo etiquetada como Bash, eis a maneira de fazer Bash (≥4): use
mapfile
com a opção-s
(pular) e-n
(contagem).Se você precisar obter a 42ª linha de um arquivo
file
:Nesse ponto, você terá uma matriz
ary
cujos campos contêm as linhas defile
(incluindo a nova linha à direita), onde pulamos as primeiras 41 linhas (-s 41
) e paramos depois de ler uma linha (-n 1
). Então essa é realmente a 42ª linha. Para imprimi-lo:Se você precisar de um intervalo de linhas, diga o intervalo de 42 a 666 (inclusive) e diga que não deseja fazer as contas sozinho e imprima-as no stdout:
Se você precisar processar essas linhas também, não é realmente conveniente armazenar a nova linha à direita. Nesse caso, use a
-t
opção (aparar):Você pode fazer com que uma função faça isso por você:
Sem comandos externos, apenas Bash embutidos!
fonte
Você também pode usar sed print e sair:
fonte
-n
opção desativa a ação padrão para imprimir todas as linhas, como você certamente descobriria com uma rápida olhada na página de manual.sed
todas assed
respostas têm a mesma velocidade. Portanto (para GNUsed
) esta é a melhorsed
resposta, pois economizaria tempo para arquivos grandes e pequenos valores de enésima linha .Você também pode usar o Perl para isso:
fonte
A solução mais rápida para arquivos grandes é sempre a cauda | cabeça, desde que as duas distâncias:
S
E
são conhecidos. Então, poderíamos usar isso:
No entanto, é apenas a contagem de linhas necessária.
Mais detalhes em https://unix.stackexchange.com/a/216614/79743
fonte
S
eE
(ie bytes, caracteres ou linhas).Todas as respostas acima respondem diretamente à pergunta. Mas aqui está uma solução menos direta, mas uma ideia potencialmente mais importante, para provocar pensamentos.
Como os comprimentos de linha são arbitrários, todos os bytes do arquivo antes da enésima linha precisam ser lidos. Se você possui um arquivo enorme ou precisa repetir essa tarefa várias vezes, e esse processo é demorado, considere seriamente se deveria armazenar seus dados de maneira diferente em primeiro lugar.
A solução real é ter um índice, por exemplo, no início do arquivo, indicando as posições em que as linhas começam. Você pode usar um formato de banco de dados ou apenas adicionar uma tabela no início do arquivo. Como alternativa, crie um arquivo de índice separado para acompanhar seu arquivo de texto grande.
por exemplo, você pode criar uma lista de posições de caracteres para novas linhas:
então leia com
tail
, que na verdadeseek
está diretamente no ponto apropriado no arquivo!por exemplo, para obter a linha 1000:
fonte
Como acompanhamento da resposta de benchmarking muito útil do CaffeineConnoisseur ... Eu estava curioso sobre a rapidez com que o método 'mapfile' foi comparado com os outros (como isso não foi testado), então tentei fazer uma comparação rápida e suja da velocidade como Eu tenho o bash 4 à mão. Fiz um teste do método "cauda | cabeça" (em vez de cabeça | cauda) mencionado em um dos comentários na resposta principal enquanto eu estava nisso, pois as pessoas estavam cantando louvores. Não tenho nada do tamanho do arquivo de teste usado; o melhor que pude encontrar em pouco tempo foi um arquivo de linhagem de 14M (linhas longas separadas por espaços em branco, pouco menos de 12.000 linhas).
Versão curta: mapfile aparece mais rápido que o método cut, mas mais lento que tudo o resto, então eu diria que é um fracasso. cauda | head, OTOH, parece que poderia ser o mais rápido, embora com um arquivo desse tamanho a diferença não seja tão substancial em comparação com o sed.
Espero que isto ajude!
fonte
Usando o que os outros mencionaram, eu queria que essa fosse uma função rápida e elegante no meu shell bash.
Crie um arquivo:
~/.functions
Adicione o conteúdo:
getline() { line=$1 sed $line'q;d' $2 }
Em seguida, adicione isso ao seu
~/.bash_profile
:source ~/.functions
Agora, quando você abre uma nova janela do bash, basta chamar a função da seguinte maneira:
getline 441 myfile.txt
fonte
Se você obteve várias linhas delimitadas por \ n (normalmente nova linha). Você também pode usar 'cut':
Você obterá a segunda linha do arquivo.
-f3
fornece a terceira linha.fonte
cat FILE | cut -f2,5 -d$'\n'
exibirá as linhas 2 e 5 do ARQUIVO. (Mas não preservará a ordem.)Para imprimir a enésima linha usando sed com uma variável como número da linha:
Aqui, o sinalizador '-e' serve para adicionar script ao comando a ser executado.
fonte
Muitas boas respostas já. Eu pessoalmente vou com awk. Por conveniência, se você usar o bash, basta adicionar o seguinte ao seu
~/.bash_profile
. E, da próxima vez que você fizer login (ou se você buscar seu .bash_profile após esta atualização), você terá uma nova função bacana "nésima" disponível para canalizar seus arquivos.Execute isso ou coloque-o no seu ~ / .bash_profile (se estiver usando o bash) e reabra o bash (ou execute
source ~/.bach_profile
)# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }
Então, para usá-lo, basta passar por ele. Por exemplo,:
$ yes line | cat -n | nth 5 5 line
fonte
Depois de dar uma olhada na resposta superior e na referência , implementei uma pequena função auxiliar:
Basicamente, você pode usá-lo de duas formas:
fonte
Coloquei algumas das respostas acima em um script bash curto, que você pode colocar em um arquivo chamado
get.sh
e vincular/usr/local/bin/get
(ou qualquer outro nome que você preferir).Verifique se é executável com
Vinculá-lo para torná-lo disponível no
PATH
comDesfrute de forma responsável!
P
fonte