Usando head e tail para pegar diferentes conjuntos de linhas e salvar no mesmo arquivo

Usando head e tail para pegar diferentes conjuntos de linhas e salvar no mesmo arquivo

Portanto, isto é para o dever de casa, mas não farei a pergunta específica do dever de casa.

Preciso usar head e tail para pegar diferentes conjuntos de linhas de um arquivo. Então, curta as linhas 6 a 11 e as linhas 19 a 24 e salve ambas em outro arquivo. Eu sei que posso fazer isso usando anexos como

head -11 file|tail -6 > file1; head -24 file| tail -6 >> file1. 

Mas não acho que devamos fazer isso.
Existe uma maneira específica de combinar os comandos head e tail e depois salvar no arquivo?

Responder1

Você pode fazer isso headsozinho e com aritmética básica, se agrupar comandos { ... ; }usando uma construção como

{ head -n ...; head -n ...; ...; } < input_file > output_file

onde todos os comandos compartilham a mesma entrada (obrigado@mikeserv).
Obter as linhas 6 a 11 e as linhas 19 a 24 é equivalente a:

head -n 5 >/dev/null  # dump the first 5 lines to `/dev/null` then
head -n 6             # print the next 6 lines (i.e. from 6 to 11) then
head -n 7 >/dev/null  # dump the next 7 lines to `/dev/null` ( from 12 to 18)
head -n 6             # then print the next 6 lines (19 up to 24)

Então, basicamente, você executaria:

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } < input_file > output_file

Responder2

Você pode usar a { … }construção de agrupamento para aplicar o operador de redirecionamento a um comando composto.

{ head -n 11 file | tail -n 6; head -n 24 file | tail -n 6; } >file1

Em vez de duplicar as primeiras M+N linhas e manter apenas as últimas N, você pode pular as primeiras M linhas e duplicar as próximas N. Isto émensuravelmente mais rápido em arquivos grandes. Tenha em atenção que o +Nargumento de tailnão é o número de linhas a saltar, mas um mais isso — é o número da primeira linha a imprimir com linhas numeradas a partir de 1.

{ tail -n +6 file | head -n 6; tail -n +19 file | head -n 6; } >file1

De qualquer forma, o arquivo de saída é aberto apenas uma vez, mas o arquivo de entrada é percorrido uma vez para cada trecho ser extraído. Que tal agrupar as entradas?

{ tail -n +6 | head -n 6; tail -n +14 | head -n 6; } <file >file1

Em geral, isso não funciona. (Pode funcionar em alguns sistemas, pelo menos quando a entrada é um arquivo normal.) Por quê? Por causa debuffer de entrada. A maioria dos programas, incluindo o tail, não lê sua entrada byte por byte, mas alguns kilobytes por vez, porque é mais rápido. Então taillê alguns kilobytes, pula um pouco no início, passa um pouco mais para heade para - mas o que é lido é lido e não fica disponível para o próximo comando.

Outra abordagemé usar headcanalizado para/dev/nullpara pular linhas.

{ head -n 5 >/dev/null; head -n 6; head -n 7 >/dev/null; head -n 6; } <file >file1

Novamente, não é garantido que isso funcione devido ao buffer. Acontece que funciona com o headcomando do GNU coreutils (aquele encontrado em sistemas Linux não embarcados), quando a entrada é de um arquivo normal. Isso porque uma vez que esta implementação headleu o que quer, eladefine a posição do arquivopara o primeiro byte que não foi gerado. Isso não funciona se a entrada for um tubo.

Uma maneira mais simples de imprimir diversas sequências de linhas de um arquivo é chamar uma ferramenta mais generalista comosedouestranho. (Isso pode ser mais lento, mas só importa para arquivos extremamente grandes.)

sed -n -e '6,11p' -e '19,24p' <file >file1
sed -e '1,5d' -e '12,18d' -e '24q' <file >file1
awk '6<=NR && NR<=11 || 19<=NR && NR<=24' <file >file1
awk 'NR==6, NR==11; NR==19, NR==24' <file >file1

Responder3

Eu sei que você disse que precisa usar head e tail, mas sed é definitivamente a ferramenta mais simples para o trabalho aqui.

$ cat foo
a 1 1
a 2 1
b 1 1
a 3 1
c 3 1
c 3 1
$ sed -ne '2,4p;6p' foo
a 2 1
b 1 1
a 3 1
c 3 1

Você pode até construir os blocos em uma string com algum outro processo e executá-los através do sed.

$ a="2,4p;6p"
$ sed -ne $a foo
a 2 1
b 1 1
a 3 1
c 3 1

-n nega a saída, então você especifica intervalos para imprimir com p, com o primeiro e o último número do intervalo separados por vírgula.

Dito isto, você pode executar o agrupamento de comandos sugerido por @don_crissti ou percorrer o arquivo algumas vezes com head/tail capturando um pedaço de linhas cada vez que você passa.

$ head -4 foo | tail -3; head -6 foo | tail -1
a 2 1
b 1 1
a 3 1
c 3 1

Quanto mais linhas em um arquivo e quanto mais blocos você tiver, mais eficiente será o sed.

Responder4

Use uma função bash como esta:

seq 1 30 > input.txt
f(){ head $1 input.txt | tail $2 >> output.txt ;}; f -11 -2; f -24 -3
cat output.txt
10
11
22
23
24

Isso é um pouco exagerado neste caso, mas se seus filtros ficarem maiores, isso pode se tornar uma vantagem.

informação relacionada