Ferramenta Bash para obter a enésima linha de um arquivo


606

Existe uma maneira "canônica" de fazer isso? Eu tenho usado o head -n | tail -1que 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.


10
A "maneira Unix" é encadear ferramentas que fazem bem seu respectivo trabalho. Então eu acho que você já encontrou um método muito adequado. Outros métodos incluem awke sede eu tenho certeza que alguém pode vir até com um one-liner Perl ou menos assim;)
0xC0000022L

3
O comando duplo sugere que a head | tailsolução está abaixo do ideal. Outras soluções quase ótimas foram sugeridas.
Jonathan Leffler

Você executou algum benchmark em qual solução é a mais rápida para um caso médio?
Marcin

5
Benchmarks (para um intervalo) na linha gato X à linha Y em um arquivo enorme no Unix e Linux . (cc @Marcin, no caso de você ainda está se perguntando depois de dois + anos)
Kevin

6
A head | tailsolução não funciona, se você consultar uma linha que não existe na entrada: ela imprimirá a última linha.
Jarno

Respostas:


803

heade cano com tailserá lento para um arquivo enorme. Eu sugeriria sedassim:

sed 'NUMq;d' file

Onde NUMestá o número da linha que você deseja imprimir; por exemplo, sed '10q;d' filepara imprimir a 10ª linha de file.

Explicação:

NUMqserá encerrado imediatamente quando o número da linha for NUM.

dexcluirá a linha em vez de imprimi-la; isso é inibido na última linha porque qfaz com que o restante do script seja ignorado ao sair.

Se você possui NUMuma variável, convém usar aspas duplas em vez de simples:

sed "${NUM}q;d" file

44
Para quem se pergunta, essa solução parece ser de 6 a 9 vezes mais rápida que as soluções sed -n 'NUMp'e sed 'NUM!d'propostas abaixo.
Skippy le Grand Gourou

75
Eu acho que 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.
Richard

2
@rici (revisão de comentário anterior) No Linux (Ubuntu 12.04, Fedora 20), o uso 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 o catdesempenho permanece o mesmo. Curiosamente, no OS X 10.9.3 nada disso parece fazer diferença: cat/ não cat, arquivo em cache ou não. @anubhava: o prazer é meu.
mklement0

2
@ SkippyleGrandGourou: Dada a natureza específica dessa otimização , mesmo seus intervalos de números são inúteis como uma declaração geral . A única solução geral é esta: (a) essa otimização pode ser aplicada com segurança a todas as entradas; (b) os efeitos variam de nenhum a dramático , dependendo do índice da linha procurada em relação ao número de linhas gerais.
mklement0

17
sed 'NUMqproduzirá os primeiros NUMarquivos e ;dexcluirá tudo, exceto a última linha.
Anubhava

304
sed -n '2p' < file.txt

imprimirá a segunda linha

sed -n '2011p' < file.txt

2011th line

sed -n '10,33p' < file.txt

linha 10 até linha 33

sed -n '1p;3p' < file.txt

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


6
@RafaelBarbosa <neste caso não é necessário. Simplesmente, é minha preferência usar redirecionamentos, porque eu costumava usar redirecionamentos como sed -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) :)
jm666

1
@ jm666 Na verdade, é 2 caracteres mais tempo desde que você normalmente colocaria o '<', bem como um espaço extra '' depois <como oppposed a apenas um espaço se você não tivesse usado o <:)
rasen58

2
@ rasen58 o espaço também é um personagem? :) / bem, só brincadeira - você está certo / :)
jm666

1
@duhaime, é claro, se alguém precisar fazer otimizações. Mas IMHO para os problemas "comuns" está tudo bem e a diferença é imperceptível. Além disso, o head/ tailnão resolve o sed -n '1p;3p'cenário - aka imprimir mais linhas não adjacentes ...
jm666

1
@duhaime, é claro - a nota está correta e necessária. :)
jm666

93

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:

  • Eu preciso extrair apenas um subconjunto das linhas para fazer algo útil com os dados.
  • Lendo todas as linhas que levam aos valores de que me importo vai demorar muito tempo.
  • Se a solução ler além das linhas de que me preocupo e continuar lendo o restante do arquivo, perderá tempo lendo quase 3 bilhões de linhas irrelevantes e levará 6x mais tempo do que o necessário.

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 timebuilt-in para comparar cada comando.

Linha de base

Primeiro vamos ver como a head tailsolução:

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

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:

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

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 exitporque não iria esperar a execução do arquivo completo:

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

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:

$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

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:

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

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 tailsolução. Na melhor das hipóteses, a sedsolução fornece um aumento de ~ 3% na eficiência.

(porcentagens calculadas com a fórmula % = (runtime/baseline - 1) * 100)

Linha 50.000.000

  1. 00: 01: 12.705 (-00: 00: 02.616 = -3,47%) sed
  2. 00: 01: 13.146 (-00: 00: 02.175 = -2,89%) perl
  3. 00: 01: 15,321 (+00: 00: 00.000 = + 0,00%) head|tail
  4. 00: 01: 16,583 (+00: 00: 01,262 = + 1,68%) awk
  5. 00: 05: 12,156 (+00: 03: 56,835 = + 314,43%) cut

Linha 500.000.000

  1. 00: 12: 07,050 (-00: 00: 26.160) sed
  2. 00: 12: 11,460 (-00: 00: 21.750) perl
  3. 00: 12: 33,210 (+00: 00: 00.000) head|tail
  4. 00: 12: 45,830 (+00: 00: 12.620) awk
  5. 00: 52: 01,560 (+00: 40: 31,650) cut

Linha 3.338.559.320

  1. 01: 20: 54.599 (-00: 03: 05.327) sed
  2. 01: 21: 24.045 (-00: 02: 25.227) perl
  3. 01: 23: 49,273 (+00: 00: 00.000) head|tail
  4. 01: 25: 13,548 (+00: 02: 35.735) awk
  5. 05: 47: 23.026 (+04: 24: 26.246) cut

4
Gostaria de saber quanto tempo levaria o arquivo inteiro para / dev / null. (E se este foi apenas um benchmark de disco rígido?)
sanmai

Sinto um desejo perverso de me curvar à sua propriedade de um dicionário de arquivos de texto com mais de 3 GB. Seja qual for o raciocínio, isso inclui a textualidade :)
Stabledog 04/02

51

Com awkisso é bem rápido:

awk 'NR == num_line' file

Quando isso for verdade, o comportamento padrão awké realizada: {print $0}.


Versões alternativas

Se o seu arquivo for enorme, é melhor exitdepois de ler a linha necessária. Dessa forma, você economiza tempo da CPU. Veja a comparação do tempo no final da resposta .

awk 'NR == num_line {print; exit}' file

Se você deseja fornecer o número da linha de uma variável bash, pode usar:

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent

Veja quanto tempo é economizado usando exit, especialmente se a linha estiver na primeira parte do arquivo:

# Let's create a 10M lines file
for ((i=0; i<100000; i++)); do echo "bla bla"; done > 100Klines
for ((i=0; i<100; i++)); do cat 100Klines; done > 10Mlines

$ time awk 'NR == 1234567 {print}' 10Mlines
bla bla

real    0m1.303s
user    0m1.246s
sys 0m0.042s
$ time awk 'NR == 1234567 {print; exit}' 10Mlines
bla bla

real    0m0.198s
user    0m0.178s
sys 0m0.013s

Portanto, a diferença é 0.198s vs 1.303s, cerca de 6x vezes mais rápido.


Esse método sempre será mais lento porque o awk tenta fazer a divisão de campos. A sobrecarga da divisão de campo pode ser reduzida emawk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
kvantour

O verdadeiro poder de awk neste método vem adiante quando você quer n1 linha concatenate de file1, n2 de file2, n3 ou file3 ... awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3. Com o GNU awk, isso pode ser acelerado usando awk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3.
kvantour

@kvantour de fato, o próximo arquivo do GNU awk é ótimo para essas coisas. Como FS=RSevitar a divisão de campos?
fedorqui 'SO stop prejudicar'

1
FS=RSnão evita a divisão de campos, mas apenas analisa os $ 0 e atribui apenas um campo porque não há RSin$0
kvantour

@kvantour Eu tenho feito alguns testes FS=RSe não vi diferença nos horários. Que tal eu fazer uma pergunta sobre isso para poder expandir? Obrigado!
fedorqui 'SO stop prejudicar'

29

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 -1imprimirá a 7ª linha do arquivo.

tail -n+Nimprimirá tudo a partir da linha Ne head -1fará 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 seg
  • head -N | tail -1: 4.6 seg
  • sed Nq;d: 18,8 seg

Os resultados podem diferir, mas o desempenho head | taile tail | headé, em geral, comparável para entradas menores e sedé 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:

#!/bin/bash
readonly file=tmp-input.txt
readonly size=1000000000
readonly pos=500000000
readonly retries=3

seq 1 $size > $file
echo "*** head -N | tail -1 ***"
for i in $(seq 1 $retries) ; do
    time head "-$pos" $file | tail -1
done
echo "-------------------------"
echo
echo "*** tail -n+N | head -1 ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time tail -n+$pos $file | head -1
done
echo "-------------------------"
echo
echo "*** sed Nq;d ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time sed $pos'q;d' $file
done
/bin/rm $file

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:

*** head -N | tail -1 ***
500000000

real    0m9,800s
user    0m7,328s
sys     0m4,081s
500000000

real    0m4,231s
user    0m5,415s
sys     0m2,789s
500000000

real    0m4,636s
user    0m5,935s
sys     0m2,684s
-------------------------

*** tail -n+N | head -1 ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt
500000000

real    0m6,452s
user    0m3,367s
sys     0m1,498s
500000000

real    0m3,890s
user    0m2,921s
sys     0m0,952s
500000000

real    0m3,763s
user    0m3,004s
sys     0m0,760s
-------------------------

*** sed Nq;d ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt
500000000

real    0m23,675s
user    0m21,557s
sys     0m1,523s
500000000

real    0m20,328s
user    0m18,971s
sys     0m1,308s
500000000

real    0m19,835s
user    0m18,830s
sys     0m1,004s

1
O desempenho é diferente entre head | tailvs tail | head? Ou depende de qual linha está sendo impressa (início do arquivo versus final do arquivo)?
wisbucky

1
@wisbucky Eu não tenho números concretos, mas uma desvantagem do primeiro uso da cauda seguido de uma "cabeça -1" é que você precisa saber com antecedência o comprimento total. Se você não o conhece, deve contá-lo primeiro, o que será uma perda em termos de desempenho. Outra desvantagem é que é menos intuitivo de usar. Por exemplo, se você tem o número 1 a 10 e deseja obter a 3ª linha, teria que usar "tail -8 | head -1". Isso é mais suscetível a erros do que "head -3 | tail -1".
Philipp Claßen 18/01/19

desculpe, eu deveria ter incluído um exemplo para ficar claro. head -5 | tail -1vs tail -n+5 | head -1. Na verdade, encontrei outra resposta que fez uma comparação de teste e tail | headque foi mais rápida. stackoverflow.com/a/48189289
wisbucky 19/01

1
@wisbucky Obrigado por mencionar! Fiz alguns testes e tenho que concordar que sempre foi um pouco mais rápido, independente da posição da linha do que vi. Dado isso, mudei minha resposta e também incluí a referência, caso alguém queira reproduzi-la.
Philipp Claßen 19/01/19

27

Uau, todas as possibilidades!

Tente o seguinte:

sed -n "${lineNum}p" $file

ou um deles, dependendo da sua versão do Awk:

awk  -vlineNum=$lineNum 'NR == lineNum {print $0}' $file
awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file
awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file

( Talvez você precise tentar o comando nawkougawk ).

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.



21

Esta questão está sendo etiquetada como Bash, eis a maneira de fazer Bash (≥4): use mapfilecom a opção -s(pular) e -n(contagem).

Se você precisar obter a 42ª linha de um arquivo file:

mapfile -s 41 -n 1 ary < file

Nesse ponto, você terá uma matriz arycujos campos contêm as linhas de file(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:

printf '%s' "${ary[0]}"

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:

mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"

Se você precisar processar essas linhas também, não é realmente conveniente armazenar a nova linha à direita. Nesse caso, use a -topção (aparar):

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"

Você pode fazer com que uma função faça isso por você:

print_file_range() {
    # $1-$2 is the range of file $3 to be printed to stdout
    local ary
    mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3"
    printf '%s' "${ary[@]}"
}

Sem comandos externos, apenas Bash embutidos!


11

Você também pode usar sed print e sair:

sed -n '10{p;q;}' file   # print line 10

6
A -nopçã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.
tripleee

No GNU, sed todas as sedrespostas têm a mesma velocidade. Portanto (para GNU sed ) esta é a melhor sedresposta, pois economizaria tempo para arquivos grandes e pequenos valores de enésima linha .
AGC

7

Você também pode usar o Perl para isso:

perl -wnl -e '$.== NUM && print && exit;' some.file

6

A solução mais rápida para arquivos grandes é sempre a cauda | cabeça, desde que as duas distâncias:

  • desde o início do arquivo até a linha de partida. Vamos chamá-loS
  • a distância da última linha até o final do arquivo. SejaE

são conhecidos. Então, poderíamos usar isso:

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

No entanto, é apenas a contagem de linhas necessária.

Mais detalhes em https://unix.stackexchange.com/a/216614/79743


1
Por favor, esclareça as unidades de Se E(ie bytes, caracteres ou linhas).
AGC

6

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:

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

então leia com tail, que na verdade seekestá diretamente no ponto apropriado no arquivo!

por exemplo, para obter a linha 1000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • Isso pode não funcionar com caracteres de 2 bytes / multibyte, pois o awk é "sensível a caracteres", mas a cauda não.
  • Eu não testei isso em um arquivo grande.
  • Veja também esta resposta .
  • Como alternativa - divida seu arquivo em arquivos menores!

5

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.

$ time head -11000 [filename] | tail -1
[output redacted]

real    0m0.117s

$ time cut -f11000 -d$'\n' [filename]
[output redacted]

real    0m1.081s

$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]

real    0m0.058s

$ time perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]

real    0m0.085s

$ time sed "11000q;d" [filename]
[output redacted]

real    0m0.031s

$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]

real    0m0.309s

$ time tail -n+11000 [filename] | head -n1
[output redacted]

real    0m0.028s

Espero que isto ajude!


4

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


3

Se você obteve várias linhas delimitadas por \ n (normalmente nova linha). Você também pode usar 'cut':

echo "$data" | cut -f2 -d$'\n'

Você obterá a segunda linha do arquivo. -f3fornece a terceira linha.


1
Também pode ser usado para exibir várias linhas: cat FILE | cut -f2,5 -d$'\n'exibirá as linhas 2 e 5 do ARQUIVO. (Mas não preservará a ordem.)
Andriy Makukha

2

Para imprimir a enésima linha usando sed com uma variável como número da linha:

a=4
sed -e $a'q:d' file

Aqui, o sinalizador '-e' serve para adicionar script ao comando a ser executado.


2
Os dois pontos são um erro de sintaxe e devem ser um ponto e vírgula.
Tripleee

2

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


1

Depois de dar uma olhada na resposta superior e na referência , implementei uma pequena função auxiliar:

function nth {
    if (( ${#} < 1 || ${#} > 2 )); then
        echo -e "usage: $0 \e[4mline\e[0m [\e[4mfile\e[0m]"
        return 1
    fi
    if (( ${#} > 1 )); then
        sed "$1q;d" $2
    else
        sed "$1q;d"
    fi
}

Basicamente, você pode usá-lo de duas formas:

nth 42 myfile.txt
do_stuff | nth 42

0

Coloquei algumas das respostas acima em um script bash curto, que você pode colocar em um arquivo chamado get.she vincular /usr/local/bin/get(ou qualquer outro nome que você preferir).

#!/bin/bash
if [ "${1}" == "" ]; then
    echo "error: blank line number";
    exit 1
fi
re='^[0-9]+$'
if ! [[ $1 =~ $re ]] ; then
    echo "error: line number arg not a number";
    exit 1
fi
if [ "${2}" == "" ]; then
    echo "error: blank file name";
    exit 1
fi
sed "${1}q;d" $2;
exit 0

Verifique se é executável com

$ chmod +x get

Vinculá-lo para torná-lo disponível no PATHcom

$ ln -s get.sh /usr/local/bin/get

Desfrute de forma responsável!

P

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.