Cambiar el orden de las líneas en un archivo

11

Estoy tratando de cambiar el orden de las líneas en un patrón específico. Trabajando con un archivo con muchas líneas (ej. 99 líneas). Por cada tres líneas, me gustaría que la segunda línea sea la tercera línea, y que la tercera sea la segunda línea.

EJEMPLO.

1- entrada:

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

2- Salida:

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

Respuestas:

12

Usar awky matemáticas enteras:

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

El operador de módulo realiza una división entera y devuelve el resto, por lo que para cada línea, devolverá la secuencia 1, 2, 0, 1, 2, 0 [...]. Sabiendo eso, simplemente guardamos la entrada en líneas donde el módulo es 2 para más adelante, es decir, justo después de imprimir la entrada cuando es cero.

DopeGhoti
fuente
Tenemos un pequeño defecto aquí. Vea mi respuesta, parte de mejora menor
Sergiy Kolodyazhnyy
Gracias por la buena captura; He incorporado una solución a mi respuesta en forma de NR%3 == 0 { print; print delay; delay=""} END { if(length(delay) != 0 ) { print delay }.
DopeGhoti 01 de
23
$ seq 9 | sed -n 'p;n;h;n;G;p'
1
3
2
4
6
5
7
9
8

Es decir, pborre la línea actual, obtenga la nextensión anterior, la hantigua, obtenga la línea nexterna Gy la línea retenida (añádala al espacio del patrón) y pborre el espacio del patrón de 2 líneas con las líneas tercera y segunda intercambiadas.

Stéphane Chazelas
fuente
3

Otro enfoque awk :

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

La salida:

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- extrae los siguientes 2 registros si existen

  • cada segundo y tercer registro se asignan a variables L2y L3respectivamente

RomanPerekhrest
fuente
1
Supongo que esas variables comienzan con la letra L (minúscula). Son malas opciones de legibilidad porque se parecen a los números para doce y trece. Una mejor opción podría ser line2, etc.
pausa hasta nuevo aviso.
@DennisWilliamson, cambiado a mayúsculas
RomanPerekhrest
1

Usando perly un guión corto:

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.

El script procesa todo el archivo, para cada línea (almacenada $_) obtendrá las siguientes dos líneas ( $l2y $l3) y las imprimirá en el orden solicitado: línea1, línea3, línea2.

Frank Förster
fuente
1

Una forma podría ser la siguiente:

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.

fuente
1

¿Por qué no simplemente hacer un ciclo while? En forma expandida:

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

En "formato de línea única":

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

Salidas:

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

Perl

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

La idea aquí es que usamos un operador de módulo %con una $.variable de número de línea , para determinar cuál es cada primero, cuál es cada segundo y cuál es cada tercera línea. Para cada tercera línea, el resto es 0, mientras que para cada primera y segunda línea tendrá los números correspondientes.

Prueba:

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

Mejora menor

El enfoque con el almacenamiento de la segunda línea en una variable tiene un defecto. ¿Qué sucede si la última línea es la "segunda", es decir, para ese número de línea el resto es 2? El código original en mi y la respuesta de DopeGhoti no se imprimirá My dog is orangesi omitimos la última línea. La solución para eso en ambos casos es usar el END{}bloque de código, desarmando la variable temporal después de imprimir. En otras palabras:

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

y

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

De esta manera, el código funcionará para un número arbitrario de líneas en un archivo, no solo aquellas divisibles por 3.

Solución adicional para el problema mencionado en los comentarios

En el caso de awk, si la última línea del archivo produce una salida de 1 por $. % 3, el código anterior tiene problemas para generar una nueva línea en blanco debido a la impresión incondicional de END{print delay}, ya que la printfunción mencionada en los comentarios siempre agrega nueva línea a cualquier variable en la que esté operando. En el caso de la perlversión, este problema no ocurre, ya que con la función -neflags printno se agrega la nueva línea.

Sin embargo, la solución en el caso de awk es hacer condicional, como lo menciona Dope Ghoti en los comentarios, es verificar la longitud de la variable temporal. La versión perl de la misma solución sería:

$ 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
fuente
1
Su corrección tiene una falla potencial menor propia, ya que agregará una línea de salida en blanco para los archivos con el número 'incorrecto' de líneas. He arreglado esto en mi incorporación de su mejora en mi respuesta con (for awk) NR%3 == 0 { print; print delay; delay=""} END { if(length(delay) != 0 ) { print delay }.
DopeGhoti 01 de
1
@DopeGhoti El problema no ocurre con perl, ya que la impresión de perl con -nebanderas no genera una nueva línea. De hecho, se imprime, pero es una cadena nula, sin línea nueva. No obstante, he agregado la mención del problema y la misma solución en mi respuesta. Gracias !
Sergiy Kolodyazhnyy
1

Empuje

No es adecuado para archivos largos, pero sigue siendo útil si solo estaba editando un archivo y desea, por ejemplo, reordenar algunas estrofas yaml.

Primero grabe una macro:

gg qq j ddp j q

Y luego repita el número deseado de veces:

@q @q @q ...

O solo por ejemplo

3@q

Explicación:

  • gg - ir a la primera línea
  • qq - comienza a grabar una macro
  • j - ir a la segunda línea
  • ddp: intercambia la segunda y la tercera línea
  • j - ir a la cuarta línea, es decir, a la primera de las siguientes tres líneas
  • q - detener la grabación
  • @q: reproduce la macro una vez
  • 3 @ q: reproduce la macro tres veces
Edheldil
fuente
1
En lugar de la repetición manual @q @q @q, es posible hacerlo de esta manera 3@q: repita tres veces. 100@q- Repita la macro 100 veces.
MiniMax
0

Uso: ./shuffle_lines.awk input.txt

Verifique shebang #!/usr/bin/awk -f, porque la awkubicación puede diferir en su 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
fuente