Mova as primeiras N linhas de saída para terminar sem usar arquivo temporário


11

Imagine uma saída de um comando como

44444
55555
11111
22222
33333

como posso arrancar as primeiras N linhas (as duas primeiras no exemplo acima) e anexá-las no final, mas sem usar o arquivo temporário (usando apenas tubos)?

11111
22222
33333
44444
55555

Algo do tipo | sed -n '3,5p;1,2p'(que obviamente não funciona como sed não se importa com a ordem dos comandos).


2
Por que não podemos usar um arquivo temporário?
Braiam)

Respostas:


13

Apenas copie essas linhas para o buffer de espera (exclua-as) e, quando estiver na última linha, adicione o conteúdo do buffer de espera ao espaço do padrão:

some command | sed '1,NUMBER{           # in this range
H                                       # append line to hold space and
1h                                      # overwrite if it's the 1st line
d                                       # then delete the line
}
$G'                                     # on last line append hold buffer content

Com gnu sedvocê poderia escrever como

some command | sed '1,NUMBER{H;1h;d;};$G'

Aqui está outra maneira com o ol ' ed(ele rdireciona a saída some commandpara o buffer de texto e, em seguida, mfornece linhas 1,NUMBERapós o último $):

ed -s <<IN
r ! some command
1,NUMBERm$
,p
q
IN

Observe que, conforme indicado, ambos falharão se a saída tiver menos de NUMBER+1 linhas. Uma abordagem mais sólida seria ( gnu sedsintaxe):

some command | sed '1,NUMBER{H;1h;$!d;${g;q;};};$G'

este exclui apenas as linhas desse intervalo, desde que não sejam a última linha ( $!d); caso contrário, substitui o espaço do padrão pelo conteúdo do buffer de retenção ( g) e, em seguida, quits (após a impressão do espaço do padrão atual).


2
sed -e '1,NUMBER{H;1h;d;}' -e '$G'também funciona portably (note que algumas sedimplementações não pode deter mais do que alguns kilobytes no espaço de espera, de modo NÚMERO não pode ser muito grande lá)
Stéphane Chazelas

@ StéphaneChazelas - obrigado pela contribuição - eu costumo usar um comando por linha, pois sei que é portátil - a sintaxe de várias expressões sempre foi um pouco confusa para mim, por exemplo, os documentos do posix dizem "O <bright-brace> precedido por um <newline> " , de acordo com eles, não deveria sed -e '1,NUMBER{H;1h;d' -e'}' -e '$G'?
don_crissti

4
Normalmente, um novo -esubstitui uma nova linha. d;}ainda não é POSIX, mas portátil. Isso será corrigido na próxima especificação do POSIX. Veja austingroupbugs.net/view.php?id=944#c2720
Stéphane Chazelas

2
@don_crissti obrigado! seria legal se você também pudesse incluir uma breve explicação de por que funciona. (É claro que eu vou procurá-lo, mas faria para uma resposta mais valioso.)
Peter Uhnak

Na minha cabeça, o importante 1,NUMBER{H;1h;d;}não é ter ponto e vírgula imediatamente após a chave de abertura . Isso pode ter sido apenas um bug no SunOS 4.1, sedcuja solução alternativa ainda está conectada aos meus dedos 20 anos depois.
Zwol

11

Uma awkabordagem:

cmd | awk -v n=3 '
  NR <= n {head = head $0 "\n"; next}
  {print}
  END {printf "%s", head}'

Um benefício sobre a sedabordagem de @ don_crissti é que ele ainda funciona (gera as linhas) se a saída tiver nlinhas ou menos.


Você pode substituir o código codificado \npelo ORS, para que isso funcione com outros separadores de registros (digamos que você queira usar parágrafos, etc.).
Fedorqui 20/04

6

Eu tenho xclipe com isso isso pode ser feito desta maneira:

./a_command | xclip -in && xclip -o | tail -n +3 && xclip -o | head -n 2

Aqui está sua descrição:

xclip - command line interface to X selections (clipboard)

NAME
       xclip - command line interface to X selections (clipboard)

SYNOPSIS
       xclip [OPTION] [FILE]...

DESCRIPTION
       Reads from standard in, or from one or more files, and makes the data available as an X selection for pasting into X applications. Prints current X selection to standard out.

       -i, -in
              read text into X selection from standard input or files (default)

       -o, -out
              prints the selection to standard out (generally for piping to a file or program)

3
+1 para o criativo (uso incorreto) do xclip. A resposta precisa de um servidor X acessível / em execução.
Jofel

3

Uma maneira Perl:

perl -ne '$.<3?($f.=$_):print;}{print $f'

Ou, a mesma coisa escrita de forma menos enigmática:

perl -ne 'if($.<3){ $f.=$_ } else{ print } END{print $f}'

Por exemplo:

$ cat file
44444
55555
11111
22222
33333

$ cat file | perl -ne '$.<3?($f.=$_):print;}{print $f'
11111
22222
33333
44444
55555

Explicação

  • -ne: leia o arquivo de entrada / fluxo linha por linha e aplique o script fornecido por -ecada linha.
  • $.<3: $.é o número da linha atual; portanto, mude 3para o número de linhas que você deseja alterar.
  • $.<3?($f.=$_):print;: este é o operador condicional, o formato geral é condition ? case1 : case2, ele será executado case1se conditionfor verdadeiro e case2se for falso. Aqui, se o número da linha atual for menor que 3, ele anexará a linha atual ( $_) à variável $fe, se o número da linha for maior que 3, será impresso.
  • }{ print $f: o }{é perl abreviação para END{}. Ele será executado após todas as linhas de entrada terem sido processadas. Nesse ponto, teremos coletado todas as linhas que queremos mudar e impressas todas as que queremos deixar em paz; portanto, imprima as linhas salvas como $f.

1
em relação à sua versão para golfe, alguns caracteres podem ser removidos #perl -ne '$.<3?$f.=$_:print}{print $f
123 #

1

Use POSIX ex. Sim, ele se destina à edição de arquivos, mas funcionará em um pipeline.

printf %s\\n 111 222 333 444 555 | ex -sc '1,2m$|%p|q!' /dev/stdin

Isso pode ter quaisquer comandos arbitrários adicionados no início ou no final do pipeline e funcionará da mesma maneira. Melhor ainda, dada a presença de /dev/stdin, é compatível com POSIX.

(Não sei se /dev/stdinestá especificado no POSIX ou não, mas vejo que está presente no Linux e no Mac OS X.)

Isso tem uma vantagem de legibilidade em relação ao uso seddo espaço de espera - basta dizer ex"mova essas linhas para o fim" e o faz. (O restante dos comandos significa "imprimir o buffer" e "sair", que também são legíveis.)

NB: O excomando fornecido acima falhará se for fornecido menos de 2 linhas como entrada.

Leitura adicional:


0

Um pequeno pythontrecho:

#!/usr/bin/env python3
import sys
file_ = sys.argv[1]
lines = int(sys.argv[2])
with open(file_) as f:
    f = f.readlines()
    out = f[lines:] + f[:lines]
    print(''.join(out), end='')

Passe o nome do arquivo como primeiro argumento e o número de linhas para mover como segundo argumento.

Exemplo:

$ cat input.txt
44444
55555
11111
22222
33333

$ ./sw_lines.py input.txt 2
11111
22222
33333
44444
55555

$ ./sw_lines.py input.txt 4
33333
44444
55555
11111
22222

0

Se você pode armazenar toda a saída na memória:

data=$(some command)
n=42                  # or whatever
{ tail -n +$((n+1)) <<<"$data"; head -n $n <<<"$data"; } > outputfile

0

Aqui está outra opção que requer o GNU sed:

(x=$(gsed -u 3q);cat;printf %s\\n "$x")

-utorna o GNU sedsem buffer, para que o sedcomando não consuma mais de 3 linhas de STDIN. A substituição do comando remove as linhas vazias, para que o comando não inclua linhas vazias no final da saída, se a terceira, terceira e segunda ou terceira, segunda e primeira linhas estiverem vazias.

Você também pode fazer algo assim:

tee >(sponge /dev/stdout|sed -u 3q)|sed 1,3d

Sem sponge /dev/stdout|o comando falharia com entradas longas. sponge /dev/stdouttambém pode ser substituído por tac|tac, mesmo que seja impressa, por exemplo, a\ncbse a entrada é a\nb\nconde \nestá um avanço de linha, ou com (x=$(cat);printf %s\\n "$x"), mesmo que isso remova linhas vazias do final da entrada. O comando acima exclui a primeira linha quando o número de linhas da entrada é uma ou duas.

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.