Reemplazar un personaje excepto las últimas x ocurrencias

9

Tengo un archivo que tiene un montón de nombres de host correlacionados con IP que se ve así:

x-cluster-front-1 192.168.1.2
x-cluster-front-2 192.158.1.10
y-cluster-back-1 10.1.11.99
y-cluster-back-2 10.1.157.38
int.test.example.com 59.2.86.3
super.awesome.machine 123.234.15.6

Quiero que se vea así:

x-cluster-front-1 192.168.1.2
x-cluster-front-2 192.158.1.10
y-cluster-back-1 10.1.11.99
y-cluster-back-2 10.1.157.38
int-test-example-com 59.2.86.3
super-awesome-machine 123.234.15.6

¿Cómo puedo reemplazar el. (puntos) de la primera columna con - (guión) para facilitar una ordenación por la segunda columna? Estaba pensando en usar sed para reemplazar los puntos hasta el primer espacio, o reemplazar cada punto excepto los últimos tres, pero tengo problemas para entender regex y sed. ¡Puedo realizar reemplazos simples pero esto está muy por encima de mi cabeza!

Esto es parte de un script más grande que he estado escribiendo en bash. Estoy atrapado en esta parte.

Florín
fuente

Respuestas:

7

Puedes usar AWK

awk '{gsub(/-/,".",$1);print}' infile

Explicación

awkdivide una línea en el espacio en blanco de forma predeterminada. Por lo tanto, la primera columna de la línea ( $1en awk-ese) será en la que desea realizar las sustituciones. Para este propósito, puede usar:

 gsub(regex,replacement,string)

para realizar la sustitución requerida.

Tenga en cuenta que gsubsolo es compatible con gawky nawkpara muchas distribuciones modernas, awkes un enlace suave a gawk.

Rahul Patil
fuente
1
+1 Golpéame en eso. Creo que una explicación realmente beneficiaría también al autor de la pregunta y a los futuros lectores.
Joseph R.
1
@JosephR. Lo siento, no soy bueno en la explicación, pero he intentado y actualizado ..
Rahul Patil
2
La especificación POSIX para awkse basa en nawk, por lo que todas las awkimplementaciones modernas deberían tener gsub. En Solaris, puede necesitar /usr/xpg4/bin/awko nawk.
Stéphane Chazelas
@RahulPatil Si no te importa, agregué algunas líneas que creo que ayudarían a otros.
Joseph R.
@JosephR gracias .., parece perfecto ahora .. :)
Rahul Patil
6

Si necesita hacer las sustituciones en el primer campo, lo mejor es usar la solución awk de Rahul, pero tenga en cuenta que puede afectar el espacio (los campos se reescriben con un solo espacio entre ellos).

Puede evitarlo escribiéndolo en su lugar:

perl -pe 's|\S+|$&=~tr/./-/r|e' file

La -pbandera significa "leer el archivo de entrada línea por línea e imprimir cada línea después de aplicar el script dado por -e". Entonces, el sustituto de ( s|pattern|replacement|) la primera secuencia de caracteres no espaciales ( \S+) con el patrón emparejado ( $&) después de sustituir todo .con -. El truco es usar s|||edonde el eoperador evaluará una expresión como reemplazo. Por lo tanto, puede tener un reemplazo ( tr/./-/) aplicado a la coincidencia ( $&) del anterior ( s|||e).

Si necesita sustituir cada uno .con un -excepto los últimos 3 últimos, con GNU sedy suponiendo que tiene un revcomando:

rev file | sed 's/\./-/4g' | rev
Stéphane Chazelas
fuente
1
Tenga en cuenta que la solución Perl asume la versión 5.14 o superior (para /rque funcione).
Joseph R.
3

Sed no es la herramienta más fácil para el trabajo; vea otras respuestas para obtener mejores herramientas, pero se puede hacer.

Para reemplazar .por -sólo hasta el primer espacio, el uso sen un bucle.

sed -e '
  : a                     # Label "a" for the branching command
  s/^\([^ .]*\)\./\1-/    # If there is a "." before the first space, replace it by "-"
  t a                     # If the s command matched, branch to a
'

(Tenga en cuenta que algunas implementaciones de sed no admiten comentarios en la misma línea. GNU sed sí lo hace).

Para realizar el reemplazo hasta el último espacio:

sed -e '
  : a                     # Label "a" for the branching command
  s/\.\(.* \)/-\1/        # If there is a "." before the last space, replace it by "-"
  t a                     # If the s command matched, branch to a
'

Otra técnica hace uso del espacio de retención de sed. Guarde el bit que no desea modificar en el espacio de espera, haga su trabajo, luego recupere el espacio de espera. Aquí, divido la línea en el último espacio y reemplazo los puntos por guiones en la primera parte.

sed -e '
  h           # Save the current line to the hold space
  s/.* / /    # Remove everything up to the last space
  x           # Swap the work space with the hold space
  s/[^ ]*$//  # Remove everything after the last space
  y/./-/      # Replace all "." by "-"
  G           # Append the content of the hold to the work space
  s/\n//      # Remove the newline introduced by G
'
Gilles 'SO- deja de ser malvado'
fuente
2

Dado que Rahul le dio la respuesta canónica para su caso de uso, pensé en intentar responder al problema titular: sustituir todas las ocurrencias x, excepto las últimas, de una expresión regular:

perl -pe '
    $count = tr{.}{.}; # Count '.' on the current line
    $x = 3;
    next LINE if $count <= $x;
    while(s{\.}{-}){   # Substitute one '.' with a '-'
        last if ++$i == $count - $x # Quit the loop before the last x substitutions
    }
$i = 0
' your_file

El código anterior (probado) no supone que tenga campos separados por espacios. Reemplazará todos los puntos en una línea con guiones, excepto los últimos 3 puntos. Reemplace el 3en el código a su gusto.

Joseph R.
fuente
2

Puede usar muchas herramientas diferentes para esto. Rahul Patil ya te dio gawkuno, así que aquí hay algunos otros:

  • perl

    perl -lane  '$F[0]=~s/\./-/g; print "@F"' file
    

    El -ainterruptor hace que Perl divida automáticamente las líneas de entrada en el espacio en blanco y guarde los campos resultantes en la matriz @F. El primer campo, por lo tanto, será $F[0]así que reemplazamos ( s///) todas las apariciones de .with -en el primer campo y luego imprimimos toda la matriz.

  • cáscara

     while read -r a b; do printf "%s %s\n" "${a//./-}" "$b"; done < file 
    

    Aquí, el bucle while lee el archivo y se divide automáticamente en espacios en blanco. Esto crea dos campos $firsty $rest. La construcción ${first//pattern/replacement}reemplaza todas las ocurrencias de patterncon replacement.

terdon
fuente
+1 Si bien perlrun(1)le diré que -aes "modo de división automática", prefiero pensar en él como " awkmodo": D
Joseph R.
2

Creo que esto es un poco más fácil de leer que una gran expresión regular desagradable. Básicamente, simplemente dividí la línea en dos campos en el espacio en blanco y utilicé sed en la primera parte.

while read -r host ip; do
    echo "$(sed 's/\./-/g' <<< "$host") $ip"
done < input_file

Dependiendo de su shell, también podría usar $ {host //./-} en lugar del comando sed.

maedox
fuente
0
sed 's/\./-/' <file name>

Sin usar gal final del comando puede hacer esto ... Esto simplemente reemplazará la primera aparición del patrón

sunandan
fuente