Digamos que eu tenha um arquivo de texto enorme (> 2 GB) e eu só quero cat
as linhas X
para Y
(por exemplo 57.890.000-57.890.010).
Pelo que entendi, posso fazer isso head
entrando em tail
contato ou vice-versa, ou seja,
head -A /path/to/file | tail -B
ou alternativamente
tail -C /path/to/file | head -D
onde A
, B
, C
e D
pode ser calculado a partir do número de linhas no arquivo, X
e Y
.
Mas há dois problemas com essa abordagem:
- Você tem que calcular
A
,B
,C
eD
. - Os comandos podem
pipe
um para o outro muito mais linhas do que estou interessado em ler (por exemplo, se estou lendo apenas algumas linhas no meio de um arquivo enorme)
Existe uma maneira de fazer com que o shell trabalhe e produza as linhas que eu quero? (enquanto fornece apenas X
e Y
)?
tail
cat
large-files
head
Amelio Vazquez-Reina
fonte
fonte
Respostas:
Sugiro a
sed
solução, mas por uma questão de integridade,Para cortar após a última linha:
Teste rápido:
seq 100000000 > test.in
real
tempo, como relatado porbash
é builtintime
Esses não são, de modo algum, benchmarks precisos, mas a diferença é clara e repetível o suficiente * para dar uma boa noção da velocidade relativa de cada um desses comandos.
*: Exceto entre os dois primeiros,
sed -n p;q
ehead|tail
, que parecem ser essencialmente os mesmos.fonte
tail -n +50000000 test.in | head -n10
que, diferentementetail -n-50000000 test.in | head -n10
, daria o resultado correto?tail+|head
é mais rápido em 10-15% do que o sed, eu adicionei esse benchmark.-c
para pular caracteres,tail+|head
é instantânea. Obviamente, você não pode dizer "50000000" e talvez precise procurar manualmente o início da seção que está procurando.Se você quiser as linhas X a Y inclusive (iniciando a numeração em 1), use
tail
lerá e descartará as primeiras linhas X-1 (não há como contornar isso), depois lerá e imprimirá as seguintes linhas.head
irá ler e imprimir o número solicitado de linhas e sair. Quandohead
sai,tail
recebe um sinal SIGPIPE e morre, para que não tenha lido mais do que o tamanho de um buffer (normalmente alguns kilobytes) de linhas do arquivo de entrada.Como alternativa, como sugerido pelo gorkypl , use sed:
A solução sed é significativamente mais lenta (pelo menos para os utilitários GNU e Busybox; sed pode ser mais competitivo se você extrair grande parte do arquivo em um sistema operacional em que a tubulação é lenta e a sed é rápida). Aqui estão referências rápidas no Linux; os dados foram gerados por
seq 100000000 >/tmp/a
, o ambiente é Linux / amd64,/tmp
é tmpfs e a máquina está ociosa e sem troca.Se você conhece o intervalo de bytes com o qual deseja trabalhar, pode extraí-lo mais rapidamente, pulando diretamente para a posição inicial. Mas para linhas, você precisa ler desde o início e contar novas linhas. Para extrair blocos de x inclusive para exclusivo y começando em 0, com um tamanho de bloco de b:
fonte
tail will read and discard the first X-1 line
parece ser evitado quando o número de linhas é dado a partir do final. Nesse caso, a cauda parece ler para trás a partir do final, de acordo com os tempos de execução. Por favor, leia-se:http://unix.stackexchange.com/a/216614/79743
.tail
(incluindo GNU tail) têm heurísticas para ler a partir do final. Isso melhora atail | head
solução em comparação com outros métodos.A
head | tail
abordagem é uma das melhores e mais "idiomáticas" maneiras de fazer isso:Como apontado por Gilles nos comentários, uma maneira mais rápida é
A razão pela qual isso é mais rápido é que as primeiras linhas X-1 não precisam passar pelo tubo em comparação com a
head | tail
abordagem.Sua pergunta formulada é um pouco enganadora e provavelmente explica algumas de suas dúvidas infundadas em relação a essa abordagem.
Você diz que você tem que calcular
A
,B
,C
,D
mas como você pode ver, não é necessária a contagem de linha do arquivo e, no máximo, 1 cálculo é necessário, que o shell pode fazer por você de qualquer maneira.Você teme que a tubulação leia mais linhas do que o necessário. De fato, isso não é verdade:
tail | head
é tão eficiente quanto você pode obter em termos de E / S de arquivo. Primeiro, considere a quantidade mínima de trabalho necessária: para encontrar a linha X em um arquivo, a única maneira geral de fazer isso é ler todos os bytes e parar quando você contar X símbolos de nova linha, pois não há como adivinhar o arquivo deslocamento da X 'ésima linha. Quando chegar ao * X * th linha, você tem que ler todas as linhas, a fim de imprimi-los, parando no Y linha th'. Portanto, nenhuma abordagem pode se safar da leitura de menos de linhas Y. Agora,head -n $Y
não lê mais que Ylinhas (arredondadas para a unidade de buffer mais próxima, mas os buffers, se usados corretamente, melhoram o desempenho, portanto, não é necessário se preocupar com essa sobrecarga). Além disso,tail
não lerá mais do quehead
, portanto, mostramos quehead | tail
lê o menor número possível de linhas (novamente, mais algum buffer insignificante que estamos ignorando). A única vantagem de eficiência de uma abordagem de ferramenta única que não usa tubos é menos processos (e, portanto, menos sobrecarga).fonte
A maneira mais ortodoxa (mas não a mais rápida, como observado por Gilles acima) seria usar
sed
.No seu caso:
A
-n
opção implica que apenas as linhas relevantes sejam impressas em stdout.O p no final de acabamento número de linha meios para imprimir linhas em determinado intervalo. O q na segunda parte do script economiza algum tempo, pulando o restante do arquivo.
fonte
sed
etail | head
estava em pé de igualdade, mas acontece quetail | head
é significativamente mais rápido (veja minha resposta ).tail
/head
são considerados mais "ortodoxos", já que aparar as extremidades de um arquivo é exatamente o que eles foram feitos. Nesses materiais,sed
apenas parece entrar em cena quando são necessárias substituições - e ser rapidamente retirado da cena quando algo muito mais complexo começa a acontecer, já que sua sintaxe para tarefas complexas é muito pior que o AWK, que então assume o controle. .Se soubermos o intervalo a ser selecionado, da primeira linha:
lStart
até a última linha:lEnd
poderemos calcular:Se soubermos a quantidade total de linhas:
lAll
também poderemos calcular a distância até o final do arquivo:Então conheceremos os dois:
Escolhendo o menor de qualquer um deles:
tailnumber
como este:Permite usar o comando de execução consistentemente mais rápido:
Observe o sinal de mais ("+") adicional quando
$linestart
selecionado.A única ressalva é que precisamos da contagem total de linhas e isso pode levar algum tempo adicional para ser encontrado.
Como é habitual com:
Algumas vezes medidas são:
Observe que os tempos mudam drasticamente se as linhas selecionadas estão perto do início ou do fim. Um comando que parece funcionar bem em um lado do arquivo pode ser extremamente lento no outro lado do arquivo.
fonte
Faço isso com bastante frequência e escrevi esse script. Não preciso encontrar os números das linhas, o script faz tudo.
fonte
tail|head
, que foi discutida extensivamente na pergunta e nas outras respostas, e 90% determinando os números de linha onde as strings / padrões especificados aparecem, o que não fazia parte da pergunta . PS, você deve sempre citar seus parâmetros e variáveis do shell; por exemplo, "$ 3" e "$ 4".