Implémentation d'une expression rationnelle étendue pour ajouter un nombre variable de zéros de tête en fonction de la position dans une chaîne

10

J'ai du mal à obtenir ma syntaxe sed pour ajouter un nombre variable de zéros de tête à un schéma d'organisation numérique. Les chaînes sur lesquelles j'opère apparaissent comme

1.1.1.1,Some Text Here

tirer parti de la syntaxe sed

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

Je peux obtenir la réponse

01.01.01.01,Some Text Here

Cependant, ce que je recherche, c'est quelque chose à remplir jusqu'à 2 chiffres dans les champs 2 et 3 et 3 chiffres dans le champ 4 afin que tous les éléments soient d'une longueur standard à [0-9]. [0-9] { 2}. [0-9] {2}. [0-9] {3}

1.01.01.001,Some Text Here

Pour la vie de moi, je ne sais même pas comment modifier la frontière pour inclure les paramètres nécessaires pour accrocher aux seuls chiffres après une période. Je pense que cela a quelque chose à voir avec l'utilisation du \ b que je comprends correspond à zéro caractère à la limite d'un mot, mais je ne comprends pas pourquoi mes tentatives pour ajouter un point à la correspondance échouent comme suit:

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

De plus, je m'attends à avoir des problèmes supplémentaires si la déclaration contient du texte comme:

1.1.1.1,Some Number 1 Here

Il est évident que j'ai besoin d'apprendre vraiment sed et toutes ses complexités. J'y travaille, mais je m'attends à ce que cette déclaration particulière continue de me causer des ennuis pendant un certain temps. Toute aide serait grandement appréciée.

EDIT: J'ai trouvé un moyen ... Cette déclaration semble faire ce que je cherche, mais il doit y avoir un moyen plus élégant de le faire.

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

En outre, syntaxiquement, cela causera des problèmes si un format numérique similaire apparaît dans le texte ... similaire à:

1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3

Dans ce cas, il en résultera:

1.01.01.001,Some Text Referring to Document XXX Heading 01.02.03

Résolu Merci à tous pour votre aide ici. J'ai d'abord résolu le problème avec la réponse que j'ai acceptée ci-dessous. Je sens que la solution a été déplacée dans Python dans le cadre d'une solution plus large tirant parti du tri ci-dessous:

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
la source
Cela semble faire ce que je recherche: sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//' Cependant, j'aimerais savoir s'il existe une approche plus élégante.
daijizai
1
Étrangement, inverser la chaîne, appliquer des zéros de fin , puis inverser le résultat peut atteindre votre objectif plus facilement.
roaima
2
L'utilisation printf(ou un printfappel dans Awk) peut être plus simple.
Wildcard
1
c'est certainement quelque chose qui sera plus facile à implémenter, lire, comprendre et modifier à l'avenir dans un langage comme awk ou perl (ou toute autre chose qui a printf et un découpage de champ facile).
cas
1
@Wildcard - point bien pris. Pouvez-vous m'indiquer quelque chose à propos du débogage de sed? Habituellement, je recourt à un regard prolongé ponctué d'expressions. ;) Sauf que, je casse parfois une déclaration sed en petits morceaux et essaie de faire fonctionner chacun avant de les combiner à nouveau. J'ai récemment lu un excellent tutoriel github.com/learnbyexample/Command-line-text-processing/blob/… et j'étais sûr que certains des exemples étaient erronés jusqu'à ce que j'applique un regard prolongé.
Joe

Réponses:

4

Usage: 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"

Explication:

  1. La première sous-substitution ajoute une certaine quantité de zéros à chaque numéro. 1 zéro à 2 et 3 chiffres, 2 zéro à 4 chiffres. Peu importe, combien de chiffres il y a déjà.
  2. La deuxième substitution supprime tous les zéros supplémentaires, ne laissant que la quantité de chiffres nécessaire. Les numéros 2 et 3 ne doivent contenir que 2 chiffres. Les laisse et supprime les restes. Le quatrième numéro ne doit contenir que 3 chiffres. Les laisse et supprime les restes.

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
la source
Alors qu'à la fin je viens de finir de l'écrire en Python pour plus de commodité, c'est la meilleure réponse à ma question telle qu'elle est écrite étant donné que le perl précédemment soumis a supprimé les barres obliques inverses (au moins) de la sortie. Ce 1. est une solution sed, et 2. produit la sortie appropriée sans molestation du texte. Marquage comme réponse. Merci! :-)
daijizai
@daijizai comme je l'ai déjà démontré, la perlversion ne supprime pas les barres obliques inverses.
roaima
9

bash peut gérer cela. Ce sera beaucoup plus lent 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
la source
2
Ou Awk. Mais +1 pour l'utilisation printf, l'outil sensé. (Awk a printfégalement et est mieux conçu que bashpour le traitement de texte.) Voir aussi Pourquoi l'utilisation d'une boucle shell pour traiter du texte est-elle considérée comme une mauvaise pratique?
Wildcard
5

Vous n'avez pas spécifiquement demandé de perlsolution mais en voici une quand même. Personnellement, je pense que c'est un peu plus facile à lire, surtout lorsqu'il est divisé en plusieurs lignes.

Voici d'abord le 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"'

Ses résultats:

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

Et voici le perlscript éclaté et commenté (le -ndrapeau met une while read; do ... doneboucle implicite autour du code):

($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
la source
Ironiquement, j'étais sur le point d'abandonner dans sed et de passer à awk lorsque vous avez posté cela. Cela semble correspondre au projet de loi. Je vais vérifier et revenir.
daijizai
@daijizai awkfonctionnerait aussi - même principe en utilisantprintf
roaima
La seule chose que cela échoue, je n'aurais pas pu l'imaginer, mais c'est significatif. Il semble supprimer la barre oblique inverse de la partie texte.
daijizai
@daijizai pas ici, ce n'est pas le cas. Comment alimentez-vous le texte avec une barre oblique inverse? J'ai ajouté un exemple de barre oblique inverse pour vous
roaima
Dans mon utilisation avec mon jeu de données interne, il y a des lignes avec la colonne de texte contenant des chaînes comme SOME \ Text \ Might \ Be \ Here \ 4Realz. Lorsque cet ensemble de données a été transmis à l'instruction perl, il en est résulté une réponse comme SOMETextMightBeHere4Realz
daijizai
3

Voici une approche possible:
sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'

Exemples

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

Travaillez également avec cette chaîne:

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

... et cette chaîne:

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
la source
Malheureusement, cela tombe en panne à mesure que les chiffres grimpent. Par exemple: 1.1.11.111, du texte ici est devenu: 1.1.101.11001, du texte ici
daijizai
@daijizai S'il vous plaît voir mon montage. Est-ce que cela répondrait à l'exigence?
maulinglawns
Malheureusement non, mais je pense que cela pourrait être de ma faute. Le remplissage nul doit comporter deux chiffres sur le champ 2 et 3 et 3 chiffres sur le champ 4. Essentiellement [0-9]. [0-9] {2}. [0-9] {2}. [0 -9] {3}, du texte ici
daijizai
2
perl -pe '/^\d/g && s/\G(?:(\.\K\d+(?=\.))|\.\K\d+(?=,))/sprintf "%0".($1?2:3)."d",$&/ge'

Explication:

La méthode utilisée est ici de regarder les voisinages du numérique et de prendre des mesures en fonction de cela. Ainsi, les 2e et 3e chiffres voient un point des deux côtés tandis que le 4e chiffre voit le point à gauche et une virgule à droite.

Le $ 1 est défini lorsque l'expression régulière prend le chemin des 2ème ou 3ème nombres et en conséquence le remplissage de précision est 2. OTOH, pour le 4ème nombre, le remplissage est de 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

Résultats:

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

la source