Alterar a ordem das linhas em um arquivo

11

Estou tentando alterar a ordem das linhas em um padrão específico. Trabalhando com um arquivo com muitas linhas (por exemplo, 99 linhas). Para cada três linhas, gostaria que a segunda linha fosse a terceira linha e a terceira a segunda.

EXEMPLO.

1- Entrada:

gi_1234
My cat is blue.
I have a cat.
gi_5678
My dog is orange.
I also have a dog.
...

2- Saída:

gi_1234
I have a cat.
My cat is blue.
gi_5678
I also have a dog.
My dog is orange.
...
Annick Raymond
fonte

Respostas:

12

Usando awke números inteiros:

awk 'NR%3 == 1 { print } NR%3 == 2 { delay=$0 } NR%3 == 0 { print; print delay; delay=""} END { if(length(delay) != 0 ) { print delay } }' /path/to/input

O operador de módulo executa a divisão inteira e retorna o restante, portanto, para cada linha, retornará a sequência 1, 2, 0, 1, 2, 0 [...]. Sabendo disso, apenas salvamos a entrada nas linhas em que o módulo é 2 para mais tarde - ou seja, logo após imprimir a entrada quando é zero.

DopeGhoti
fonte
Temos uma pequena falha aqui. Veja minha resposta, parte pequena melhoria
Sergiy Kolodyazhnyy 1/17/17
Obrigado pela boa captura; Eu incorporei uma correção na minha resposta na forma de NR%3 == 0 { print; print delay; delay=""} END { if(length(delay) != 0 ) { print delay }.
DopeGhoti 01/06
23
$ seq 9 | sed -n 'p;n;h;n;G;p'
1
3
2
4
6
5
7
9
8

Ou seja, plimpe a linha atual, obtenha a next, hantiga, obtenha a next Ge a linha retida (anexa ao espaço do padrão) e plimpe esse espaço de padrão de 2 linhas com a terceira e a segunda linhas trocadas.

Stéphane Chazelas
fonte
3

Outra abordagem inábil :

awk '{print $0; if ((getline L2)>0 && (getline L3)>0){ print L3 ORS L2 }}' file

A saída:

gi_1234
I have a cat.
My cat is blue.
gi_5678
I also have a dog.
My dog is orange.

  • (getline L2)>0 && (getline L3)>0- extrai os próximos 2 registros, se existirem

  • cada segundo e terceiro registros são atribuídos L2e L3variáveis ​​respectivamente

RomanPerekhrest
fonte
1
Estou assumindo que essas variáveis ​​começam com a letra L (minúscula). São más escolhas de legibilidade, porque se parecem com os números dos doze e dos treze anos. Uma escolha melhor pode ser line2etc.
Pausado até novo aviso.
@DennisWilliamson, alterado para maiúsculo
RomanPerekhrest
1

Usando perle um script curto:

user@pc:~$ cat input.txt 
gi_1234
My cat is blue.
I have a cat.
gi_5678
My dog is orange.
I also have a dog.

user@pc:~$ perl -ne '$l2=<>; $l3=<>; print $_,$l3,$l2;' input.txt 
gi_1234
I have a cat.
My cat is blue.
gi_5678
I also have a dog.
My dog is orange.

O script processa o arquivo inteiro. Para cada linha (armazenada em $_), ela obtém as próximas duas linhas ( $l2e $l3) e as imprime na ordem solicitada: linha1, linha3, linha2.

Frank Förster
fonte
1

Uma maneira pode ser a seguinte:

sed -e '
   /\n/s/\(.*\)\(\n\)\(.*\)/\3\2\1/;//b
   $!N;$q;N;                            # load up the pattern space with 3 lines provided eof not reached
   P;D;                                 # first just print the first line then interchange the two and print them
' yourfile

Alternativamente,

perl -ne 'print $_, reverse scalar <>, scalar <>' yourfile

Resultados

gi_1234
I have a cat.
My cat is blue.
gi_5678
I also have a dog.
My dog is orange.

fonte
1

Por que não fazer um loop while? Em forma expandida:

( while read a
  do
    read b
    read c
    echo "$a"
    echo "$c"
    echo "$b"
  done
) < input.txt

No "formato de linha única":

( while read a ; do read b ; read c ; echo "$a" ; echo "$c" ; echo "$b" ; done) < input.txt

Saídas:

gi_1234
I have a cat.
My cat is blue.
gi_5678
I also have a dog.
My dog is orange.
Stephen Quan
fonte
1

Perl

perl -ne 'print if $.%3==1;$var=$_ if $.%3==2;print $_ . $var if $.%3==0' input.txt

A idéia aqui é que usamos o operador módulo %com $.variável de número de linha , para descobrir qual é o primeiro, qual é o segundo e qual é a terceira linha. Para cada terceira linha, o restante é 0, enquanto para cada primeira e segunda linhas, ele terá números correspondentes.

Teste:

$ cat input.txt                                                                                                          
gi_1234
My cat is blue.
I have a cat.
gi_5678
My dog is orange.
I also have a dog.

$ perl -ne 'print if $.%3==1;$var=$_ if $.%3==2;print $_ . $var if $.%3==0' input.txt                                    
gi_1234
I have a cat.
My cat is blue.
gi_5678
I also have a dog.
My dog is orange.

Melhoria menor

A abordagem para armazenar a segunda linha em uma variável tem uma falha. E se a última linha for a "segunda", ou seja, para o número restante da linha for 2? O código original da minha resposta e da DopeGhoti não será impresso My dog is orangese deixarmos de fora a última linha. A correção para isso em ambos os casos é usar o END{}bloco de código, desabilitando a variável temporária após a impressão. Em outras palavras:

$ awk 'NR%3 == 1 { print } NR%3 == 2 { delay=$0 } NR%3 == 0 { print; print delay;delay=""}END{print delay}' input.txt

e

$ perl -ne '$s=$_ if $.%3==2;print $_ . $s and $s="" if $.%3==0 or $.%3==1;END{print $s}' input.txt 

Dessa forma, o código funcionará para um número arbitrário de linhas em um arquivo, não apenas para as divisíveis por 3.

Correção adicional para o problema mencionado nos comentários

No caso do awk, se a última linha do arquivo produzir uma saída de 1 por $. % 3, o código anterior tem o problema de gerar nova linha em branco devido à impressão incondicional de END{print delay}, uma vez que a printfunção mencionada nos comentários sempre anexa nova linha a qualquer variável em que esteja operando. No caso da perlversão, esse problema não ocorre, pois a função with -neflags printnão anexa a nova linha.

No entanto, a correção no caso do awk é tornar condicional, como mencionado por Dope Ghoti nos comentários, é verificar o tamanho da variável temporária. A versão perl da mesma correção seria:

$ perl -ne '$s=$_ if $.%3==2;print $_ . $s and $s="" if $.%3==0 or $.%3==1;END{print $s if length $s}' input.txt 
Sergiy Kolodyazhnyy
fonte
1
Sua correção possui uma falha menor em potencial, pois anexa uma linha de saída em branco para arquivos com o número 'errado' de linhas. Corrigi isso na minha incorporação da sua melhoria na minha resposta com (para awk) NR%3 == 0 { print; print delay; delay=""} END { if(length(delay) != 0 ) { print delay }.
precisa saber é o seguinte
1
@DopeGhoti O problema não ocorre com o perl, pois a impressão do perl com -nesinalizadores não gera uma nova linha. Na verdade, é impresso, mas é uma sequência nula, sem nova linha final. No entanto, adicionei a menção do problema e a mesma correção na minha resposta. Obrigado !
Sergiy Kolodyazhnyy
1

Vim

Não é adequado para arquivos longos, mas ainda é útil se você estivesse apenas editando um arquivo e desejasse, por exemplo, reorganizar algumas estrofes do yaml.

Primeiro grave uma macro:

gg qq j ddp j q

E repita o número desejado de vezes:

@q @q @q ...

Ou apenas por exemplo

3@q

Explicação:

  • gg - vá para a primeira linha
  • qq - começa a gravar uma macro
  • j - vá para a segunda linha
  • ddp - troca a segunda e a terceira linha
  • j - vá para a quarta linha, ou seja, para a primeira das próximas três linhas
  • q - parar a gravação
  • @q - reproduz a macro uma vez
  • 3 @ q - reproduza a macro três vezes
Edheldil
fonte
1
Em vez de repetição manual @q @q @q, é possível fazer desta maneira 3@q- repita três vezes. 100@q- repita a macro 100 vezes.
MiniMax
0

Uso: ./shuffle_lines.awk input.txt

Verifique shebang #!/usr/bin/awk -f, porque o awklocal pode ser diferente no seu sistema.

#!/usr/bin/awk -f

{
    if ((NR + 1) % 3 == 0) {
        buffer = $0;
    } else if (NR % 3 == 0) {
        print $0 ORS buffer;
        buffer = "";
    } else {
        print;
    }
}
MiniMax
fonte