Manipulación de texto con sed


12

Actualmente, tengo varios archivos de texto con contenido que se ve así (con muchas líneas):

565 0 10 12 23 18 17 25
564 1 7 12 13 16 18 40 29 15

Deseo cambiar cada línea para que tenga el siguiente formato:

0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

¿Hay alguna forma de hacer lo anterior usando sed? ¿O necesito recurrir a Python?

Respuestas:


22

Podría hacerlo con sed, sí, pero otras herramientas son más simples. Por ejemplo:

$ awk '{
        printf "%s ", $2; 
        for(i=3;i<=NF;i++){
            printf "%s:%s:1 ",$1,$(i) 
        }
        print ""
       }' file 
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1 

Explicación

awk se dividirá cada línea de entrada en el espacio en blanco (por defecto), el ahorro de cada campos como $1, $2, $N. Entonces:

  • printf "%s ", $2; imprimirá el segundo campo y un espacio final.
  • for(i=3;i<=NF;i++){ printf "%s:%s:1 ",$1,$(i) }: iterará sobre los campos 3 al último campo ( NFes el número de campos) y para cada uno de ellos imprimirá el primer campo, a :, luego el campo actual y a :1.
  • print "" : esto solo imprime una nueva línea final.

O Perl:

$ perl -ane 'print "$F[1] "; print "$F[0]:$_:1 " for @F[2..$#F]; print "\n"' file 
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1 

Explicación

Las -amarcas se perlcomportan como awky dividen su entrada en espacios en blanco. Aquí, los campos se almacenan en la matriz @F, lo que significa que el primer campo será $F[0]el segundo, $F[1]etc. Entonces:

  • print "$F[1] " : imprime el segundo campo.
  • print "$F[0]:$_:1 " for @F[2..$#F];: iterar sobre los campos 3 hasta el último campo ( $#Fes el número de elementos en la matriz @F, por lo que @F[2..$#F]toma un segmento de matriz que comienza en el tercer elemento hasta el final de la matriz) e imprime el primer campo, a :, luego el campo actual y un :1.
  • print "\n" : esto solo imprime una nueva línea final.

12

Aquí hay un horrible sed ¡camino!

$ sed -r 's/^([0-9]+) ([0-9]+) ([0-9]+)/\2 \1:\3:1/; :a s/([0-9]+)(:[0-9]+:1) ([0-9]+)( |$)/\1\2 \1:\3:1 /; t a; s/ $//' file
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1 
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

Más legible:

sed -r '
s/^([0-9]+) ([0-9]+) ([0-9]+)/\2 \1:\3:1/
:a 
s/([0-9]+)(:[0-9]+:1) ([0-9]+)( |$)/\1\2 \1:\3:1 /
t a
s/ $//'

Notas

  • -r usar ERE
  • s/old/new/reemplazar oldconnew
  • ^([0-9]+) guardar algunos números al comienzo de la línea
  • \1 referencia al primer patrón guardado
  • :a etiquetar esta sección del guión a
  • ( |$) ya sea un espacio o el final de la línea
  • t pruebe si el último reemplazo fue exitoso; si fue así, ejecute el siguiente comando
  • aencuentra la etiqueta :ay vuelve a hacerlo
  • s/ $// eliminar el espacio final

Entonces, después de agregar la estructura a la primera parte, encontramos repetidamente la última instancia de la estructura y la aplicamos al siguiente número ...

Pero estoy de acuerdo en que otras herramientas lo hacen más fácil ...


Estaba esperando su solución sed: D
Ravexina

: D me tomó un tiempo @Ravexina - Creo que Muru puede hacer uno más limpio
Zanna

5

Con awk:

awk '{printf "%s ",$2; for (i=3; i<=NF; i++) printf $1":"$i":1 "; printf "\n"}' file

o con bash:

while read -r -a a; do                  # read line to array a
  printf "%s " ${a[1]}                  # print column #1
  for ((i=2;i<${#a[@]};i++)); do        # loop from column #2 to number of columns
    printf "%s " "${a[0]}:${a[$i]}:1"   # print content/values
  done
  echo                                  # print line break
done < file                             # read file from stdin

Salida:

0 565: 10: 1 565: 12: 1 565: 23: 1 565: 18: 1 565: 17: 1 565: 25: 1 
1 564: 7: 1 564: 12: 1 564: 13: 1 564: 16: 1 564: 18: 1 564: 40: 1 564: 29: 1 564: 15: 1 

5

Bueno, puedes hacerlo en sed, pero Python también funciona.

$ ./reformatfile.py  input.txt                                                                        
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1

Los contenidos de la reformatfile.pyson así:

#!/usr/bin/env python3
import sys

with open(sys.argv[1]) as fd:
    for line in fd:
        words = line.strip().split()
        pref = words[0]
        print(words[1],end=" ")
        new_words = [ ":".join([pref,i,"1"]) for i in words[2:] ]
        print(" ".join(new_words))

¿Como funciona esto? Realmente no hay nada particularmente especial. Abrimos el primer argumento de la línea de comandos como archivo para leer y procedemos a dividir cada línea en "palabras" o elementos individuales. Las primeras palabras se vuelven prefvariables, e imprimimos en el segundo elemento stdout (palabras [1]) que termina con espacio. A continuación, construimos un nuevo conjunto de "palabras" a través de la comprensión de la lista y la .join()función en una lista temporal de pref, cada palabra y cadena "1". El último paso es imprimirlos


4

Con awk:

awk '{printf("%s ", $2); for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i);\
          printf("%s:%s:1\n", $1, $NF)}' file.txt

Se trata de formatear los campos separados por espacios en el formato deseado:

  • printf("%s ", $2) imprime el segundo campo con un espacio final

  • for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i) itera sobre los campos 3º al segundo e imprime los campos en el formato deseado (primer campo, luego dos puntos, luego el campo actual, luego dos puntos, finalmente 1) con un espacio final

  • printf("%s:%s:1\n", $1, $NF) imprime el último campo con nueva línea

Ejemplo:

% cat file.txt
565 0 10 12 23 18 17 25
564 1 7 12 13 16 18 40 29 15

% awk '{printf("%s ", $2); for(i=3; i<NF; i++) printf("%s:%s:1 ", $1, $i); printf("%s:%s:1\n", $1, $NF)}' file.txt
0 565:10:1 565:12:1 565:23:1 565:18:1 565:17:1 565:25:1
1 564:7:1 564:12:1 564:13:1 564:16:1 564:18:1 564:40:1 564:29:1 564:15:1
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.