
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, p
imprima a linha atual, pegue a n
ext, h
antigo, pegue a n
ext G
e a linha retida (anexe-a ao espaço padrão) e p
imprima esse espaço padrão de 2 linhas com a terceira e a segunda linhas trocadas.
Responder2
Usando awk
matemá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 perl
e 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 ( $l2
e $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 orange
se 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 print
a função mencionada nos comentários sempre acrescenta nova linha a qualquer variável em que esteja operando. No caso da perl
versão esse problema não ocorre, pois com a função -ne
flags print
nã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