Implementar una expresión regular extendida para agregar un número variable de ceros a la izquierda en función de la posición en una cadena

10

Tengo problemas para reducir mi sintaxis sed para agregar un número variable de ceros a un esquema organizativo numérico. Las cadenas en las que estoy operando aparecen como

1.1.1.1,Some Text Here

aprovechando la sintaxis sed

sed -r ":r;s/\b[0-9]{1,$((1))}\b/0&/g;tr"

Soy capaz de obtener la respuesta

01.01.01.01,Some Text Here

Sin embargo, lo que estoy buscando es algo para rellenar con ceros hasta 2 dígitos en los campos 2 y 3 y 3 dígitos en el campo 4 para que todos los elementos tengan una longitud estándar en [0-9]. [0-9] { 2}. [0-9] {2}. [0-9] {3}

1.01.01.001,Some Text Here

Por mi vida, ni siquiera puedo imaginar cómo modificar el límite para incluir los parámetros necesarios para ajustar solo a los números después de un período. Creo que tiene algo que ver con el uso de la \ b que entiendo coincide con cero caracteres en un límite de palabra, pero no entiendo por qué mis intentos de agregar un punto a la coincidencia fallan de la siguiente manera:

sed -r ":r;s/\.\b[0-9]{1,$((1))}\b/0&/g;tr"
sed -r ":r;s/\b\.[0-9]{1,$((1))}\b/0&/g;tr"
Both cause the statement to hang

sed -r ":r;s/\b[0-9]\.{1,$((1))}\b/0&/g;tr"
sed -r ":r;s/\b[0-9]{1,$((1))}\.\b/0&/g;tr"
sed -r ":r;s/\b[0-9]{1,$((1))}\b\./0&/g;tr"
cause the statement to output:

1.01.01.1,Some Text Here

Además, espero tener problemas adicionales si la declaración contiene texto como:

1.1.1.1,Some Number 1 Here

Es una conclusión inevitable que necesito aprender realmente sed y todas sus complejidades. Estoy trabajando en eso, pero espero que esta declaración en particular continúe causándome problemas por un tiempo. Cualquier ayuda sería muy apreciada.

EDITAR: He descubierto una forma ... Esta declaración parece hacer lo que estoy buscando, pero debe haber una forma más elegante de hacerlo.

sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//'

Además, sintácticamente, esto causará problemas si aparece un formato de número similar en el texto ... similar a:

1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3

En cuyo caso resultará en:

1.01.01.001,Some Text Referring to Document XXX Heading 01.02.03

Resuelto Gracias a todos por su ayuda aquí. Inicialmente resolví el problema con la respuesta que acepté a continuación. Tengo la sensación de haber movido la solución a Python como parte de una solución más grande que aprovecha el siguiente tipo:

def getPaddedKey(line):
    keyparts = line[0].split(".")
    keyparts = map(lambda x: x.rjust(5, '0'), keyparts)
    return '.'.join(keyparts)

s=sorted(reader, key=getPaddedKey)
daijizai
fuente
Esto parece hacer lo que estoy buscando: sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//' Sin embargo, me encantaría saber si hay un enfoque más elegante.
daijizai
1
Extrañamente, invirtiendo la cadena, aplicando ceros finales y luego invirtiendo el resultado puede lograr su objetivo más fácilmente.
roaima
2
Usar printf(o una printfllamada dentro de Awk) puede ser más sencillo.
Comodín
1
Esto es definitivamente algo que será más fácil de implementar, leer, comprender y modificar en el futuro en un lenguaje como awk o perl (o cualquier otra cosa que tenga printf y división de campo fácil).
cas
1
@Wildcard - punto bien tomado. ¿Me puede señalar algo sobre la depuración de sed? Por lo general, recurro a la mirada prolongada puntuada por improperios. ;) Salvo eso, a veces rompo una declaración sed en partes más pequeñas y trato de hacer que cada una funcione antes de combinarlas nuevamente. Recientemente leí un gran tutorial github.com/learnbyexample/Command-line-text-processing/blob/… y estaba seguro de que algunos de los ejemplos estaban equivocados hasta que apliqué una mirada prolongada.
Joe

Respuestas:

4

Uso: leading_zero.sh input.txt

#!/bin/bash

sed -r '
    s/\.([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,3},)/.0\1.0\2.00\3/
    s/\.0*([0-9]{2})\.0*([0-9]{2})\.0*([0-9]{3})/.\1.\2.\3/
' "$1"

Explicación:

  1. La primera subtitulación agrega cierta cantidad de ceros a cada número. 1 cero a 2 y 3 números, 2 cero a 4 números. No importa, cuántos dígitos ya hay.
  2. La segunda sustitución elimina todos los ceros adicionales, dejando solo la cantidad necesaria de números. Los números 2 y 3 deben contener solo 2 dígitos. Los deja y quita los restos. El cuarto número debe contener solo 3 dígitos. Los deja y quita los restos.

input.txt

1.1.1.1,Some Text Here
1.1.1.1,Some Text Here
1.11.1.11,Some Text Referring to Document XXX Heading 1.2.3
1.1.1.1,Some Text Here
1.1.11.111,Some Text Referring to Document XXX Heading 1.2.3
1.11.1.1,Some Text Here

output.txt

1.01.01.001,Some Text Here
1.01.01.001,Some Text Here
1.11.01.011,Some Text Referring to Document XXX Heading 1.2.3
1.01.01.001,Some Text Here
1.01.11.111,Some Text Referring to Document XXX Heading 1.2.3
1.11.01.001,Some Text Here
MiniMax
fuente
Si bien al final terminé escribiendo esto en Python por conveniencia, esta es la mejor respuesta a mi pregunta tal como está escrita, dado que el perl enviado anteriormente eliminó barras diagonales (al menos) de la salida. Esta 1. es una solución sed, y 2. produce la salida adecuada sin molestar el texto. Marcado como respuesta. ¡Gracias! :-)
daijizai
@daijizai como ya lo he demostrado, la perlversión no elimina las barras invertidas.
roaima
9

bash puede manejar esto. Sin embargo, será mucho más lento que Perl:

echo "1.1.1.1,Some Text Here" | 
while IFS=., read -r a b c d text; do
    printf "%d.%02d.%02d.%03d,%s\n" "$a" "$b" "$c" "$d" "$text"
done
1.01.01.001,Some Text Here
Glenn Jackman
fuente
2
O Awk Pero +1 por usar printf, la herramienta sensata. (Awk printftambién tiene y está mejor diseñado que bashpara el procesamiento de texto). Consulte también ¿Por qué usar un bucle de shell para procesar texto se considera una mala práctica?
Comodín
5

No ha pedido específicamente una perlsolución, pero aquí hay una de todos modos. Personalmente, creo que esto es un poco más fácil de leer, especialmente cuando se divide en varias líneas.

Primero aquí está el one-liner:

(
    echo '1.2.3.4,Some Text Here'
    echo '1.01.01.1,Some Text Here'
    echo '1.1.1.1,Some Number 1 Here'
    echo '1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3'
    echo '1.2.3.4,Some \n \s \text'
) |
perl -ne '($ip, $text) = split(/,/, $_, 2); $ip = sprintf("%1d.%02d.%03d.%03d", split(/\./, $ip)); print "$ip,$text"'

Sus resultados:

1.02.003.004,Some Text Here
1.01.001.001,Some Text Here
1.01.001.001,Some Number 1 Here
1.01.001.001,Some Text Referring to Document XXX Heading 1.2.3
1.02.003.004,Some \n \s \text

Y aquí está el perlscript desglosado y comentado (la -nbandera pone un while read; do ... donebucle implícito alrededor del código):

($ip, $text) = split(/,/, $_, 2);                # Split line into two parts by comma
@octets = split(/\./, $ip)                       # Split IP address into octets by dots
$ip = sprintf("%1d.%02d.%03d.%03d", @octets);    # Apply the formatting
print "$ip,$text"                                # Output the two parts
roaima
fuente
Irónicamente, estaba a punto de rendirme en sed y pasar a awk cuando publicaste esto. Parece encajar en la factura. Lo comprobaré y volveré.
daijizai
@daijizai awktambién funcionaría - mismo principio usandoprintf
roaima
Lo único que falla en esto no podría haber anticipado, pero es significativo. Parece eliminar la barra invertida de la porción de texto.
daijizai
@daijizai no está aquí, no lo hace. ¿Cómo está alimentando el texto con una barra invertida? He agregado un ejemplo de barra invertida para usted
roaima
En mi uso con mi conjunto de datos interno, hay filas con la columna de texto que contienen cadenas como ALGUNOS \ Texto \ Podría \ Ser \ Aquí \ 4Realz. Cuando este conjunto de datos se pasó a la instrucción perl, resultó en una respuesta como SOMETextMightBeHere4Realz
daijizai
3

Aquí hay un posible enfoque:
sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'

Ejemplos

echo "1.11.111.1111,Some Text Here" | sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'
1.011.0111.001111,Some Text Here

También trabaje con esta cadena:

echo "1.1.1.1,Some Number 1 Here" | sed -E 's/([0-9]\.)/0\1/g;s/.//;s/([0-9],)/00\1/'
1.01.01.001,Some Number 1 Here

... y esta cadena:

echo "1.2.2101.7191,Some Text Here" | sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'
1.02.02101.007191,Some Text Here
maulinglawns
fuente
Desafortunadamente, esto se rompe a medida que los números suben. Por ejemplo: 1.1.11.111, Algunos textos aquí se convirtieron en: 1.1.101.11001, Algunos textos aquí
daijizai
@daijizai Por favor vea mi edición. ¿Cumpliría esto el requisito?
maulinglawns
Lamentablemente no, pero creo que podría ser mi culpa. Las necesidades de relleno cero de dos dígitos arriba en el campo 2 y 3 y 3 dígitos en el campo 4. Esencialmente [0-9]. [0-9] {2}. [0-9] {2}. [0 -9] {3}, texto aquí
daijizai
2
perl -pe '/^\d/g && s/\G(?:(\.\K\d+(?=\.))|\.\K\d+(?=,))/sprintf "%0".($1?2:3)."d",$&/ge'

Explicación:

El método utilizado aquí es observar los vecindarios de los números y tomar medidas basadas en eso. Entonces, el segundo y tercer números ven un punto en ambos lados, mientras que el cuarto número ve un punto a la izquierda y una coma a la derecha.

El $ 1 se establece cuando la expresión regular toma el camino del segundo o tercer nums y, en consecuencia, el relleno de precisión es 2. OTOH, para el cuarto número, el relleno es 3.

% cat file.txt

1.00.3.4,Some Text Here
1.01.01.1,Some Text Here
1.0.01.1,Some Number 1 Here
1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3.4
1.2.3.4,Some \n \s \text

Resultados:

1.00.03.004,Some Text Here
1.01.01.001,Some Text Here
1.00.01.001,Some Number 1 Here
1.01.01.001,Some Text Referring to Document XXX Heading 1.2.3.4
1.02.03.004,Some \n \s \text

fuente