Alterar a ordem das linhas em um arquivo

Alterar a ordem das linhas em um arquivo

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

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.
...

Responder1

$ seq 9 | sed -n 'p;n;h;n;G;p'
1
3
2
4
6
5
7
9
8

Ou seja, pimprima a linha atual, pegue a next, hantigo, pegue a next Ge a linha retida (anexe-a ao espaço padrão) e pimprima esse espaço padrão de 2 linhas com a terceira e a segunda linhas trocadas.

Responder2

Usando awkmatemática inteira:

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 módulo realiza a divisão inteira e retorna o resto, portanto para cada linha retornará a sequência 1, 2, 0, 1, 2, 0 [...]. Sabendo disso, apenas salvamos a entrada nas linhas onde o módulo é 2 para mais tarde - ou seja, logo após imprimir a entrada quando ela for zero.

Responder3

Usando perle um pequeno script:

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 $_) ele obterá as próximas duas linhas ( $l2e $l3) e as imprimirá na ordem solicitada: linha1, linha3, linha2.

Responder4

Perl

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

A ideia aqui é usar o operador módulo %com $.variável de número de linha, para descobrir qual é a primeira, qual é a cada segundo e qual é a cada terceira linha. Para cada 3ª linha o resto é 0, enquanto para cada 1ª e 2ª linha 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.

Pequena melhoria

A abordagem de armazenar a segunda linha em uma variável tem uma falha. E se a última linha for a "segunda", ou seja, para esse número de linha o resto for 2? O código original na minha resposta e na do DopeGhoti não será impresso My dog is orangese deixarmos de fora a última linha. A solução para isso em ambos os casos é usar END{}bloco de código, com desativação da 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 aquelas divisíveis por 3.

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

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

Porém, a solução no caso do awk é tornar condicional, como mencionado por Dope Ghoti nos comentários é verificar o comprimento 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 

informação relacionada