Usando cabeça e cauda para pegar diferentes conjuntos de linhas e salvando no mesmo arquivo

10

Portanto, isso é para trabalhos de casa, mas não farei a pergunta específica de trabalhos de casa.

Preciso usar cabeça e cauda para pegar diferentes conjuntos de linhas de um arquivo. Assim como as linhas 6-11 e 19-24 e salve-as em outro arquivo. Eu sei que posso fazer isso usando anexar como

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

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

user2709291
fonte
1
Eles estão especificamente pedindo para você usar heade tail? Nesse caso, sua solução é praticamente o melhor que você pode fazer. Se você tem permissão para usar outros programas sedou awkpode oferecer soluções melhores (ou seja, com menos invocações de processos).
n.st
Sim, eles estão nos pedindo para usar cabeça e cauda. Obrigado pela sua resposta.
user2709291
Só mais uma coisa que posso acrescentar: Você pode contornar o redirecionamento de saída anexando ( >>), colocando os dois comandos em parênteses para redirecionar sua produção concatenadas: (head -11 file | tail -6; head -24 file | tail -6) > file1. Realmente se resume à preferência pessoal, que é melhor.
n.st
Obrigado que vai funcionar muito bem. Eu realmente gostei disso.
precisa saber é o seguinte

Respostas:

11

Você pode fazer isso com headaritmética sozinha e 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-11 e 19-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
don_crissti
fonte
6

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 linhas M + N e manter apenas o último N, você pode pular as primeiras linhas M e duplicar o próximo N. Isso é notavelmente mais rápido em arquivos grandes . Observe que o +Nargumento de tailnão é o número de linhas a serem ignoradas, mas uma mais a que é - é o número da primeira linha a ser impressa com linhas numeradas 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 fragmento 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 comum.) Por que? Por causa do buffer de entrada . A maioria dos programas, inclusive tail, não lê sua entrada byte a byte, mas alguns kilobytes por vez, porque é mais rápido. Então taillê alguns kilobytes, pula um pouco no começo, passa um pouco mais para heade para - mas o que é lido é lido e não está disponível para o próximo comando.

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

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

Novamente, isso não garante que funcione, devido ao armazenamento em buffer. Por acaso, funciona com o headcomando do GNU coreutils (o encontrado em sistemas Linux não incorporados), quando a entrada é de um arquivo comum. Isso porque uma vez que essa implementação headleu o que deseja, ela define a posição do arquivo para o primeiro byte que não foi gerado. Isso não funciona se a entrada for um pipe.

Uma maneira mais simples de imprimir várias seqüências de linhas de um arquivo é chamar uma ferramenta mais generalista, como sed ou awk . (Isso pode ser mais lento, mas importa apenas 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
Gilles 'SO- parar de ser mau'
fonte
2
Isso não funciona, é um comportamento padrão especificado - embora certamente, como você diz, um canal não seja uma fonte de entrada confiável para entrada compartilhada. PADRÕES DE DESCRIÇÃO DE UTILIDADE : Quando um utilitário padrão lê um arquivo de entrada procurável e termina sem um erro antes de chegar ao final do arquivo, o utilitário garante que o deslocamento do arquivo na descrição do arquivo aberto seja posicionado corretamente após o último byte processado por a utilidade.
mikeserv
2

Sei que você disse que precisa usar cabeça e cauda, ​​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 outro processo e executá-lo 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 e especifica os intervalos a serem impressos 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 pelo @don_crissti ou percorrer o arquivo algumas vezes com a cabeça / cauda pegando um pedaço de linhas toda vez que passar.

$ 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 mais blocos você tiver, mais sed será eficiente.

Nomes falsos
fonte
2

Com sedvocê pode fazer:

sed '24q;1,5d;12,18d' <infile >outfile

... Possivelmente uma solução mais eficiente poderia ser obtida head. Don já demonstrou como isso pode funcionar muito bem, mas eu também tenho brincado com isso. Algo que você pode fazer para lidar com este caso específico:

for   n in 5 6 7 6
do    head -n"$n" >&"$((1+n%2))"
done  <infile >outfile 2>/dev/null

... que chamaria headquatro vezes escrevendo para outfileou para, /dev/nulldependendo se o valor dessa iteração $né um número par ou ímpar.

Para casos mais gerais, juntei isso de outras coisas que já tinha:

somehead()( 
### call it like:
### somehead -[repeat] [-][numlines]* <infile >outfile
    set -e -- "${1#-}" "$@"                             #-e for arg validation
    r=; cd -- "${TMP:-/tmp}"                            #go to tmp
    dd bs=4096 of="$$$$" <&4 2>&3 &                     #dd <in >tmpfile &bg
    until [ -s "$$$$" ]; do :; done                     #wait while tmpfile empty
    exec <"$$$$" 4<&-;   rm "$$$$"                      #<tmpfile; rm tmpfile
    [ "$3${1}0" -ne "$3${2#?}0" ]          ||           #validate args - chk $1
            shift "$(((r=-${1:--1})||1))"; shift        #shift 1||2
    while [ "$(((r+=(_n=1))-1))" -ne 0 ]   &&           #while ! $rptmax &&
          IFS= read -r l                   &&           #      ! EOF     &&
          printf "%.$(($1>0?${#l}+1:0))s" "$l           #      ? printf  do
";  do    for n do [ "${n#-}" -gt 0 ]      || exit      #args all -[nums>0]
          head "-n$((${n#-}-_n))" >&"$((n>(_n=0)?1:3))" #head -n?$1 >?[+-]
    done; done                                          #done and done
)   4<&0 3>/dev/null                                    #4<for dd 3>for head

Isso pode ser feito da seguinte maneira:

 seq 100 | somehead -1 -5 6 -7 6

... que imprime ...

6
7
8
9
10
11
19
20
21
22
23
24

Ele espera que seu primeiro argumento seja uma contagem de repetição prefixada com a -ou, na sua falta, apenas a -. Se uma contagem for fornecida, ela repetirá o padrão de linha fornecido nos argumentos a seguir tantas vezes quanto especificado e parará assim que isso for feito.

Para cada argumento a seguir, ele interpretará um número inteiro negativo para indicar uma contagem de linhas que deve ser gravada /dev/nulle um número inteiro positivo para indicar uma contagem de linhas que deve ser gravada stdout.

Portanto, no exemplo acima, ele imprime as primeiras 5 linhas para /dev/null, as próximas 6 para stdout, as próximas 7 para /dev/nullnovamente e as próximas 6 novamente para stdout. Depois de atingir o último de seus argumentos e alternar completamente a -1contagem de repetições, ele sai. Se o primeiro argumento tivesse sido, -2ele repetiria o processo mais uma vez, ou -pelo maior tempo possível.

Para cada ciclo de arg, o whileloop é processado uma vez. No topo de cada loop, a primeira linha de stdiné lida na variável shell $l. Isso é necessário porque while head </dev/null; do :; donese repetirá indefinidamente - headindica em seu retorno quando ele atinge o final do arquivo. Portanto, a verificação no EOF é dedicada reade printfescreverá $lmais uma nova linha stdoutapenas se o segundo argumento for um número inteiro positivo.

A readverificação complica um pouco o loop, porque imediatamente após outro loop ser chamado - um forloop que itera sobre args, 2-$#conforme representado em $ncada iteração do whileloop pai . Isso significa que, para cada iteração, o primeiro argumento deve ser decrementado em um do valor especificado na linha de comando, mas todos os outros devem manter seus valores originais e, portanto, o valor do $_nmarcador var é subtraído de cada um, mas apenas mantém um valor valor maior que 0 para o primeiro argumento.

Isso constitui o loop principal da função, mas a maior parte do código está na parte superior e destina-se a permitir que a função armazene de maneira limpa até mesmo um pipe como entrada. Isso funciona chamando primeiro um background ddpara copiar seu arquivo tmpfile na saída em tamanhos de bloco de 4k por peça. A função então configura um loop de espera - que quase nunca deve completar nem um único ciclo completo - apenas para garantir que ddtenha feito pelo menos uma única gravação no arquivo antes que a função substitua seu stdin por um descritor de arquivo vinculado ao tmpfile e depois, desconecta imediatamente o arquivo comrm. Isso permite que a função processe o fluxo de maneira confiável, sem a necessidade de interrupções ou limpeza - assim que a função liberar sua reivindicação no arquivo fd, o tmpfile deixará de existir porque seu único link do sistema de arquivos nomeado já foi removido.

mikeserv
fonte
0

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 exagero nesse caso, mas se seus filtros aumentarem, pode se tornar um benefício.

mkalkov
fonte