Implementare una regexp estesa per aggiungere un numero variabile di zeri iniziali in base alla posizione in una stringa


10

Ho problemi a ridurre la sintassi sed per aggiungere un numero variabile di zeri iniziali a uno schema organizzativo numerico. Le stringhe su cui sto operando appaiono come

1.1.1.1,Some Text Here

sfruttando la sintassi sed

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

Sono in grado di suscitare la risposta

01.01.01.01,Some Text Here

Tuttavia, quello che sto cercando è qualcosa da riempire con zero fino a 2 cifre nei campi 2 e 3 e 3 cifre nel campo 4 in modo che tutti gli articoli abbiano una lunghezza standard a [0-9]. [0-9] { 2}. [0-9] {2}. [0-9] {3}

1.01.01.001,Some Text Here

Per la mia vita non riesco nemmeno a capire come modificare il limite per includere i parametri necessari per agganciare solo i numeri dopo un punto. Penso che abbia qualcosa a che fare con l'uso del \ b che capisco corrisponda a zero caratteri al limite di una parola, ma non capisco perché i miei tentativi di aggiungere un punto alla corrispondenza falliscano come segue:

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

Inoltre, mi aspetto di avere ulteriori problemi se la dichiarazione contiene testo come:

1.1.1.1,Some Number 1 Here

È una conclusione scontata che ho bisogno di imparare davvero sed e tutte le sue complessità. Ci sto lavorando, ma mi aspetto che questa particolare affermazione continui a causarmi problemi per un po '. Qualsiasi aiuto sarebbe molto apprezzato.

EDIT: ho trovato un modo ... Questa affermazione sembra fare quello che sto cercando, ma deve esserci un modo più elegante per farlo.

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

Inoltre, sintatticamente ciò causerà problemi se un formato numerico simile appare nel testo ... simile a:

1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3

In tal caso si tradurrà in:

1.01.01.001,Some Text Referring to Document XXX Heading 01.02.03

Risolto Grazie a tutti per il vostro aiuto qui. Inizialmente ho risolto il problema con la risposta che ho accettato di seguito. Ho sentito spostare la soluzione in Python come parte di una soluzione più ampia sfruttando il tipo di seguito:

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

s=sorted(reader, key=getPaddedKey)

Questo sembra fare quello che sto cercando: sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//' tuttavia, mi piacerebbe sapere se esiste un approccio più elegante.
Daijizai,

1
Stranamente, invertire la stringa, applicare zeri finali e quindi invertire il risultato può raggiungere il tuo obiettivo più facilmente.
roaima,

2
L'uso printf(o una printfchiamata in Awk) potrebbe essere più semplice.
Wildcard il

1
questo è sicuramente qualcosa che sarà più facile da implementare, leggere, comprendere e modificare in futuro in un linguaggio come awk o perl (o qualsiasi altra cosa che abbia printf e una facile suddivisione dei campi).
Cas

1
@Wildcard - punto ben ripreso. Puoi indicarmi qualcosa sul debugging di sed? Di solito, ricorro a uno sguardo prolungato punteggiato da imprecazioni. ;) A parte questo, a volte spezzo un'istruzione sed in pezzi più piccoli e provo a far funzionare ognuno prima di combinarli di nuovo. Di recente ho letto un ottimo tutorial github.com/learnbyexample/Command-line-text-processing/blob/… ed ero sicuro che alcuni esempi fossero sbagliati fino a quando non ho applicato uno staring prolungato.
Joe,

Risposte:


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"

Spiegazione:

  1. La prima sottotitolazione aggiunge un certo numero di zeri a ciascun numero. 1 zero a 2 e 3 numeri, 2 zero a 4 numeri. Non importa, quante cifre ci sono già.
  2. La seconda sostituzione rimuove tutti gli zeri extra, lasciando solo la quantità necessaria di numeri. I numeri 2 e 3 devono contenere solo 2 cifre. Li lascia e rimuove i resti. Il quarto numero dovrebbe contenere solo 3 cifre. Li lascia e rimuove i resti.

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

Mentre alla fine ho appena finito di scrivere questo script in Python per convenienza, questa è la migliore risposta alla mia domanda come scritta dato che il perl precedentemente inviato rimuoveva le barre rovesciate (almeno) dall'output. Questa 1. è una soluzione sed e 2. produce l'output corretto senza molestie del testo. Contrassegnare come risposta. Grazie! :-)
daijizai,

@daijizai come ho già dimostrato, la perlversione non rimuove le barre rovesciate.
roaima,

9

bash può gestirlo. Sarà molto più lento del perl però:

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

2
O Awk. Ma +1 per l'utilizzo printf, lo strumento sensibile. (Awk ha printfanche ed è progettato meglio di quello bashper l'elaborazione del testo.) Vedi anche Perché usare un loop di shell per elaborare il testo è considerato una cattiva pratica?
Wildcard il

5

Non hai specificamente richiesto una perlsoluzione, ma eccone comunque una. Personalmente penso che questo sia un po 'più facile da leggere, specialmente se suddiviso in più righe.

Il primo è il 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"'

I suoi risultati:

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

Ed ecco lo perlscript suddiviso e commentato (la -nbandiera mette un while read; do ... doneciclo implicito attorno al codice):

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

Ironia della sorte, stavo per arrendermi in sed e passare a awk quando hai pubblicato questo. Sembra adattarsi al conto. Lo controllerò e torno.
Daijizai,

Anche @daijizai awkavrebbe funzionato - lo stesso principio usandoprintf
roaima il

L'unica cosa che fallisce non avrei potuto anticiparlo, ma è significativa. Sembra rimuovere la barra rovesciata dalla porzione di testo.
Daijizai,

@daijizai non qui no. Come lo stai alimentando con una barra rovesciata? Ho aggiunto un esempio rovesciato per te
roaima,

Nel mio uso con il mio set di dati interno ci sono righe con la colonna di testo che contengono stringhe come SOME \ Text \ Might \ Be \ Here \ 4Realz. Quando questo set di dati è stato passato all'istruzione perl ha prodotto una risposta come SOMETextMightBeHere4Realz
daijizai,

3

Ecco un possibile approccio:
sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'

Esempi

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

Funziona anche con questa stringa:

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

... e questa stringa:

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

Sfortunatamente questo si interrompe quando i numeri salgono. Ad esempio: 1.1.11.111, Some Text Here È diventato: 1.1.101.11001, Some Text Here
daijizai,

@daijizai Vedi la mia modifica. Questo soddisferebbe il requisito?
Maulinglawns,

Sfortunatamente no, ma penso che potrebbe essere colpa mia. È necessario riempire di zero due cifre sul campo 2 e 3 e 3 cifre sul campo 4. In sostanza [0-9]. [0-9] {2}. [0-9] {2}. [0 -9] {3}, Some Text Here
daijizai,

2
perl -pe '/^\d/g && s/\G(?:(\.\K\d+(?=\.))|\.\K\d+(?=,))/sprintf "%0".($1?2:3)."d",$&/ge'

Spiegazione:

Il metodo usato qui è quello di guardare i quartieri dei numeri e agire in base a quello. Quindi, il 2o e il 3o numero vedono un punto su entrambi i lati mentre il 4o numero vede il punto a sinistra e una virgola a destra.

Il $ 1 è impostato quando il regex prende il percorso del 2o o 3o numero e di conseguenza il riempimento di precisione è 2. OTOH, per il 4o numero, il riempimento è 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

risultati:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.