Qual é mais rápido para excluir a primeira linha do arquivo ... sed ou tail?

14

Nesta resposta ( como posso remover a primeira linha de um arquivo com sed? ), Há duas maneiras de excluir o primeiro registro em um arquivo:

sed '1d' $file >> headerless.txt

** ---------------- OU ---------------- **

tail -n +2 $file >> headerless.txt

Pessoalmente, acho que o tail opção é cosmeticamente mais agradável e mais legível, mas provavelmente porque sou desafiada por sed.

Qual método é o mais rápido?

WinEunuuchs2Unix
fonte
5
Não é uma resposta, mas uma possível consideração é que sedé mais portátil: "+2" para tailfunciona bem no Ubuntu, que usa o GNU tail, mas não funciona no BSD tail.
John N
A @JohnN agradece por compartilhar a tailfalta de compatibilidade entre plataformas.
WinEunuuchs2Unix
3
@ John N "+2" para a cauda funciona bem em Maio Mac rodando Sierra que afirma usar o comando tail BSD
Nick Sillito
Urgh, você está certo - acabei de executá-lo novamente e desta vez verifiquei a entrada. O que eu deveria ter feito pela primeira vez. Também é POSIX. / desliza, envergonhado.
John N
2
@ JohnN Você não está completamente errado. No passado, o UNIX não fornecia a -nopção e usava a sintaxe tail +2 $file. Veja freebsd.org/cgi/… É possível que você estivesse pensando nisso, e não em um dos BSDs modernos.
hvd

Respostas:

28

Desempenho de sedvs. tailpara remover a primeira linha de um arquivo

TL; DR

  • sed é muito poderoso e versátil, mas é isso que o torna lento, especialmente para arquivos grandes com muitas linhas.

  • tail faz apenas uma coisa simples, mas que faz bem e rápido, mesmo para arquivos maiores com muitas linhas.

Para arquivos pequenos e médios, sede tailestá executando de maneira semelhante rápida (ou lenta, dependendo de suas expectativas). No entanto, para arquivos de entrada maiores (vários MBs), a diferença de desempenho aumenta significativamente (uma ordem de magnitude para arquivos na faixa de centenas de MBs), com taildesempenho claramente superior.sed .

Experimentar

Preparações Gerais:

Nossos comandos para analisar são:

sed '1d' testfile > /dev/null
tail -n +2 testfile > /dev/null

Observe que estou canalizando a saída para /dev/null cada momento para eliminar a saída do terminal ou as gravações de arquivo como gargalo de desempenho.

Vamos configurar um disco RAM para eliminar a E / S do disco como gargalo em potencial. Eu pessoalmente tenho uma tmpfsmontada, /tmpentão simplesmente coloquei a minha testfilelá para este experimento.

Depois, estou criando um arquivo de teste aleatório contendo uma quantidade especificada de linhas $numoflinescom comprimento e dados aleatórios usando este comando (observe que definitivamente não é o ideal, fica muito lento para cerca de> 2 milhões de linhas, mas quem se importa, não é o coisa que estamos analisando):

cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n "$numoflines" > testfile

Oh, aliás. meu laptop de teste está executando o Ubuntu 16.04, 64 bits em uma CPU Intel i5-6200U. Apenas para comparação.

Cronometrando arquivos grandes:

Configurando uma enorme testfile:

A execução do comando acima numoflines=10000000produziu um arquivo aleatório contendo 10 milhões de linhas, ocupando um pouco mais de 600 MB - é enorme, mas vamos começar com isso, porque podemos:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

Execute a execução cronometrada com nosso enorme testfile:

Agora vamos fazer apenas uma única execução cronometrada com os dois comandos primeiro para estimar com que magnitude estamos trabalhando.

$ time sed '1d' testfile > /dev/null
real    0m2.104s
user    0m1.944s
sys     0m0.156s

$ time tail -n +2 testfile > /dev/null
real    0m0.181s
user    0m0.044s
sys     0m0.132s

Já vemos um resultado realmente claro para arquivos grandes, tailé uma magnitude mais rápida que sed. Mas apenas por diversão e para ter certeza de que não há efeitos colaterais aleatórios fazendo uma grande diferença, vamos fazer isso 100 vezes:

$ time for i in {1..100}; do sed '1d' testfile > /dev/null; done
real    3m36.756s
user    3m19.756s
sys     0m15.792s

$ time for i in {1..100}; do tail -n +2 testfile > /dev/null; done
real    0m14.573s
user    0m1.876s
sys     0m12.420s

A conclusão permanece a mesma, sedé ineficiente para remover a primeira linha de um arquivo grande, taildeve ser usada lá.

E sim, eu sei que as construções de loop do Bash são lentas, mas estamos fazendo relativamente poucas iterações aqui e o tempo que um loop simples leva não é significativo em comparação com os sed/ taildurações de qualquer maneira.

Cronometrando arquivos pequenos:

Configurando um pequeno testfile:

Agora, para concluir, vejamos o caso mais comum de você ter um pequeno arquivo de entrada no intervalo de kB. Vamos criar um arquivo de entrada aleatória numoflines=100, parecido com este:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

$ head -n 3 testfile 
tYMWxhi7GqV0DjWd
pemd0y3NgfBK4G4ho/
aItY/8crld2tZvsU5ly

Execute a execução cronometrada com nosso pequeno testfile:

Como podemos esperar que o tempo para esses arquivos pequenos esteja no intervalo de alguns milissegundos de experiência, vamos fazer 1000 iterações imediatamente:

$ time for i in {1..1000}; do sed '1d' testfile > /dev/null; done
real    0m7.811s
user    0m0.412s
sys     0m7.020s

$ time for i in {1..1000}; do tail -n +2 testfile > /dev/null; done
real    0m7.485s
user    0m0.292s
sys     0m6.020s

Como você pode ver, os horários são bastante semelhantes, não há muito o que interpretar ou pensar. Para arquivos pequenos, ambas as ferramentas são igualmente adequadas.

Byte Commander
fonte
+1 por responder, obrigado. Eu editei a pergunta original (desculpe) com base no comentário de Serg que também awkpode fazer isso. Minha pergunta original foi baseada no link que encontrei em primeiro lugar. Depois de todo o seu trabalho árduo, informe se devo remover awkcomo candidato à solução e retornar o foco ao escopo original do projeto sede somente tail.
WinEunuuchs2Unix
Que sistema é esse? No meu mac (ferramentas BSD), testar em / usr / share / dict / words me dá 0,09s para sed e 0,19s para cauda (e awk 'NR > 1', curiosamente).
21416 Kevin
5

Aqui está outra alternativa, usando apenas bash builtins e cat:

{ read ; cat > headerless.txt; } < $file

$fileé redirecionado para o { }agrupamento de comandos. O readsimplesmente lê e descarta a primeira linha. O restante do fluxo é então canalizado para o catqual o grava no arquivo de destino.

No meu Ubuntu 16.04, o desempenho disso e a tailsolução são muito semelhantes. Eu criei um arquivo de teste largish com seq:

$ seq 100000000 > 100M.txt
$ ls -l 100M.txt 
-rw-rw-r-- 1 ubuntu ubuntu 888888898 Dec 20 17:04 100M.txt
$

tail solução:

$ time tail -n +2 100M.txt > headerless.txt

real    0m1.469s
user    0m0.052s
sys 0m0.784s
$ 

cat/ braçadeira:

$ time { read ; cat > headerless.txt; } < 100M.txt 

real    0m1.877s
user    0m0.000s
sys 0m0.736s
$ 

No entanto, eu só tenho uma VM do Ubuntu à mão no momento e vi variações significativas no tempo de ambas, embora todas estejam no mesmo estádio.

Trauma Digital
fonte
1
+1 como resposta, obrigado. Essa é uma solução muito interessante e eu amo os chavetas e a leitura da direita para a esquerda via ordem hierárquica do bash. (não tenho certeza se eu escrevi isso corretamente). É possível atualizar sua resposta com o tamanho do arquivo de entrada e com os resultados do benchmark de tempo, se isso for fácil o suficiente?
WinEunuuchs2Unix
@ WinEunuuchs2Unix Timings adicionados, embora não sejam muito confiáveis, pois estão em uma VM. No momento, não tenho uma instalação simples do Ubuntu.
Digital Trauma
Eu não acho que VM vs Bare Metal importa quando você está comparando VM a VM de qualquer maneira. Obrigado pela prova de tempo. Eu provavelmente iria, tailmas ainda acho que a readopção é muito legal.
WinEunuuchs2Unix
4

Experimentando meu sistema e prefixando cada comando com time , obtive os seguintes resultados:

sed:

real    0m0.129s
user    0m0.012s
sys     0m0.000s

e cauda:

real    0m0.003s
user    0m0.000s
sys     0m0.000s

o que sugere que, no meu sistema, pelo menos, o AMD FX 8250 executando o Ubuntu 16.04, a cauda é significativamente mais rápida. O arquivo de teste tinha 10.000 linhas com um tamanho de 540k. O arquivo foi lido de um disco rígido.

Nick Sillito
fonte
+1 por responder, obrigado. Em um teste separado no AU Chatroom, um usuário mostrou que a cauda é 10 vezes mais rápida (2,31 segundos) do que sed (21,86 segundos) usando um RAMDisk com arquivo de 61 MB. Editei sua resposta para aplicar blocos de código, mas convém editá-la também com o tamanho do arquivo usado.
WinEunuuchs2Unix
@Serg Absolutamente justo que esta é apenas uma resposta anedótica e, potencialmente, você poderia obter resultados diferentes com diferentes configurações de hardware, teste de arquivos diferentes etc.
Nick Sillito
2
O arquivo não está no cache, quando o uso sedpode desempenhar um fator nesse resultado, é a ordem em que você os testou.
Minix
que tipo de sistema? Como eu comentei em outro post aqui, no meu mac sedfoi duas vezes mais rápido.
Kevin
1

Não há uma maneira objetiva de dizer o que é melhor, porque sede tailnão são as únicas coisas executadas em um sistema durante a execução do programa. Muitos fatores, como E / S de disco, E / S de rede, CPU interrompem para processos de maior prioridade - todos influenciam a rapidez com que seu programa será executado.

Ambos são escritos em C, portanto, este não é um problema de idioma, mas mais um problema ambiental. Por exemplo, eu tenho SSD e no meu sistema isso levará tempo em microssegundos, mas para o mesmo arquivo no disco rígido, levará mais tempo porque os HDDs são significativamente mais lentos. Portanto, o hardware também desempenha um papel nisso.

Lembre-se de algumas coisas ao considerar qual comando escolher:

  • Qual é o seu propósito ? sedé um editor de fluxo para transformar texto. tailé para gerar linhas de texto específicas. Se você quiser lidar com linhas e imprimi-las apenas, use tail. Se você deseja editar o texto, use sed.
  • tailtem uma sintaxe muito mais simples do que sed, portanto, use o que você pode ler e o que os outros podem ler.

Outro fator importante é a quantidade de dados que você está processando. Arquivos pequenos não oferecem diferença de desempenho. A imagem fica interessante quando você lida com arquivos grandes. Com um BIGFILE.txt de 2 GB, podemos ver que sedhá muito mais chamadas do sistema do que taile é consideravelmente mais lento.

bash-4.3$ du -sh BIGFILE.txt 
2.0G    BIGFILE.txt
bash-4.3$ strace -c  sed '1d' ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.38    0.079781           0    517051           read
 40.62    0.054570           0    517042           write
  0.00    0.000000           0        10         1 open
  0.00    0.000000           0        11           close
  0.00    0.000000           0        10           fstat
  0.00    0.000000           0        19           mmap
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         7         7 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.134351               1034177        11 total
bash-4.3$ strace -c  tail  -n +2 ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.30    0.148821           0    517042           write
 37.70    0.090044           0    258525           read
  0.00    0.000000           0         9         3 open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         7           fstat
  0.00    0.000000           0        10           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.238865                775615         7 total
Sergiy Kolodyazhnyy
fonte
+1 por responder, obrigado. Mas não tenho certeza se esse comentário está me ajudando a decidir qual comando devo usar ...
WinEunuuchs2Unix 20/12/16
@ WinEunuuchs2Unix Bem, você perguntou qual comando é melhor, por isso estou respondendo exatamente a essa pergunta. Qual comando escolher, é com você. Se você pode ler tailmelhor do que sed- use isso. Eu pessoalmente usaria, pythonou awkmelhor, sedporque pode ficar complexo. Além disso, se você está preocupado com o desempenho, vamos encarar a realidade - você está vendo resultados em microssegundos aqui. Você não sentirá diferença, a menos que seja um arquivo enorme no intervalo de gigabytes que você está tentando ler #
Sergiy Kolodyazhnyy
Ah, eu também gostaria de receber uma awkresposta:) ... Minha pergunta foi baseada em outra sessão de perguntas e respostas da UA (no link) e nunca foi mencionada awk. Concordo que a diferença horária é nominal em arquivos pequenos. Eu estava apenas tentando desenvolver alguns bons hábitos.
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix Claro, aqui está: awk 'NR!=1' input_file.txt . Dá-me igualmente o mesmo resultado, cerca de 150 milissegundos, mesmo número para ambos taile sed. Mas, de novo, eu estou usando SSD, então eu diria que é o disco rígido e a CPU que importam, não o comando.
Sergiy Kolodyazhnyy
1
@Serg, mesmo com apenas um arquivo de 60 MB contendo 1 milhão de linhas, 1000 execuções seddemoram mais de 3 minutos, enquanto são tailnecessários apenas 20 segundos. Isso não é que grande ainda, na verdade, definitivamente não é na faixa GB.
Byte Commander
1

A resposta principal não levou em consideração o disco > /dev/null

se você tem um arquivo grande e não deseja criar uma duplicata temporária no seu disco, tente vim -c

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time sed -i '1d' testfile

real    0m59.053s
user    0m9.625s
sys     0m48.952s

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time vim -e -s testfile -c ':1d' -c ':wq'

real    0m8.259s
user    0m3.640s
sys     0m3.093s

Editar: se o arquivo for maior que a memória disponível vim -cnão funcionar, parece que não é inteligente o suficiente para fazer uma carga incremental do arquivo

StevenWernerCS
fonte
0

Outras respostas mostram bem o que é melhor criar um novo arquivo com a primeira linha ausente. Se você quiser editar um arquivo em vez de criar um novo arquivo, aposto edque seria mais rápido, porque não deveria criar um novo arquivo. Mas você precisa pesquisar como remover uma linha edporque eu a usei apenas uma vez.

akostadinov
fonte