Implementieren eines erweiterten regulären Ausdrucks zum Hinzufügen einer variablen Anzahl führender Nullen basierend auf der Position in einer Zeichenfolge


10

Ich habe Probleme, meine sed-Syntax zu ändern, um einem numerischen Organisationsschema eine unterschiedliche Anzahl führender Nullen hinzuzufügen. Die Zeichenfolgen, mit denen ich arbeite, sehen wie folgt aus

1.1.1.1,Some Text Here

Nutzung der sed-Syntax

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

Ich kann die Antwort auslösen

01.01.01.01,Some Text Here

Was ich jedoch suche, ist etwas, das bis zu 2 Stellen in den Feldern 2 und 3 und 3 Stellen in Feld 4 auf Null gesetzt werden kann, damit alle Elemente eine Standardlänge von [0-9] haben. [0-9] { 2}. [0-9] {2}. [0-9] {3}

1.01.01.001,Some Text Here

Für mein Leben kann ich nicht einmal herausfinden, wie ich die Grenze so ändern kann, dass sie die Parameter enthält, die erforderlich sind, um nach einem Punkt nur noch Zahlen zu erfassen. Ich denke, es hat etwas mit der Verwendung von \ b zu tun, von dem ich verstehe, dass es an einer Wortgrenze mit Null Zeichen übereinstimmt, aber ich verstehe nicht, warum meine Versuche, der Übereinstimmung einen Punkt hinzuzufügen, wie folgt fehlschlagen:

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

Außerdem erwarte ich zusätzliche Probleme, wenn die Anweisung Text enthält wie:

1.1.1.1,Some Number 1 Here

Es ist eine ausgemachte Sache, dass ich sed und all seine Komplexität wirklich lernen muss. Ich arbeite daran, aber erwarte, dass diese spezielle Aussage mir noch eine Weile Ärger bereiten wird. Jede Hilfe wäre sehr dankbar.

EDIT: Ich habe einen Weg gefunden ... Diese Aussage scheint das zu tun, wonach ich suche, aber es muss einen eleganteren Weg geben, dies zu tun.

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

Auch syntaktisch verursacht dies Probleme, wenn ein ähnliches Zahlenformat im Text erscheint ... ähnlich wie:

1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3

In diesem Fall führt dies zu:

1.01.01.001,Some Text Referring to Document XXX Heading 01.02.03

Gelöst Vielen Dank für Ihre Hilfe hier. Ich habe das Problem zunächst mit der Antwort gelöst, die ich unten akzeptiert habe. Ich habe das Gefühl, dass die Lösung als Teil einer größeren Lösung, die die folgende Sortierung nutzt, in Python verschoben wurde:

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

s=sorted(reader, key=getPaddedKey)

Dies scheint das zu tun, wonach ich suche: sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//' Ich würde jedoch gerne wissen, ob es einen eleganteren Ansatz gibt.
Daijizai

1
Seltsamerweise kann das Umkehren der Zeichenfolge, das Anwenden von nachgestellten Nullen und das anschließende Umkehren des Ergebnisses Ihr Ziel leichter erreichen.
Roaima

2
Die Verwendung printf(oder ein printfAnruf innerhalb von Awk) ist möglicherweise einfacher.
Wildcard

1
Dies ist definitiv etwas, das in Zukunft in einer Sprache wie awk oder perl (oder irgendetwas anderem mit printf und einfacher Feldaufteilung) einfacher zu implementieren, zu lesen, zu verstehen und zu modifizieren sein wird.
Cas

1
@ Wildcard - Punkt gut gemacht. Können Sie mich auf etwas über das Debuggen von sed hinweisen? Normalerweise greife ich auf längeres Starren zurück, das von Sprengsätzen unterbrochen wird. ;) Ansonsten zerlege ich manchmal eine sed-Aussage in kleinere Stücke und versuche, jedes zum Laufen zu bringen, bevor ich sie wieder kombiniere. Ich habe kürzlich ein großartiges Tutorial gelesen: github.com/learnbyexample/Command-line-text-processing/blob/… und ich war mir sicher, dass einige der Beispiele falsch waren, bis ich länger starrte.
Joe

Antworten:


4

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

Erläuterung:

  1. Bei der ersten Substitution wird jeder Zahl eine bestimmte Anzahl von Nullen hinzugefügt. 1 Null bis 2 und 3 Zahlen, 2 Null bis 4 Zahlen. Es spielt keine Rolle, wie viele Ziffern bereits vorhanden sind.
  2. Die zweite Substitution entfernt alle zusätzlichen Nullen und lässt nur die benötigte Anzahl von Zahlen übrig. 2 und 3 Zahlen sollten nur 2 Ziffern enthalten. Verlässt sie und entfernt Reste. Die vierte Nummer sollte nur 3 Ziffern enthalten. Verlässt sie und entfernt Reste.

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

Während ich dies aus Gründen der Zweckmäßigkeit nur in Python geschrieben habe, ist dies die beste Antwort auf meine Frage, da der zuvor eingereichte Perl (zumindest) Backslashes aus der Ausgabe entfernt hat. Diese 1. ist eine sed-Lösung und 2. erzeugt die richtige Ausgabe ohne Belästigung des Textes. Als Antwort markieren. Vielen Dank! :-)
Daijizai

@daijizai Wie ich bereits gezeigt habe, entfernt die perlVersion keine Backslashes.
Roaima

9

Bash kann damit umgehen. Es wird allerdings viel langsamer als Perl sein:

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
Oder Awk. Aber +1 für die Verwendung printf, das sinnvolle Werkzeug. (Awk hat printfauch und ist besser als bashfür die Textverarbeitung konzipiert.) Siehe auch Warum wird die Verwendung einer Shell-Schleife zum Verarbeiten von Text als schlechte Praxis angesehen?
Wildcard

5

Sie haben nicht speziell nach einer perlLösung gefragt , aber hier ist trotzdem eine. Persönlich denke ich, dass dies etwas einfacher zu lesen ist, insbesondere wenn es in mehrere Zeilen unterteilt ist.

Zuerst hier ist der Einzeiler:

(
    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"'

Seine Ergebnisse:

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

Und hier ist das perlSkript ausgebrochen und kommentiert (das -nFlag setzt eine implizite while read; do ... doneSchleife um den 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

Ironischerweise wollte ich gerade in sed aufgeben und zu awk wechseln, als du das gepostet hast. Es scheint die Rechnung zu passen. Ich werde es überprüfen und zurückkommen.
Daijizai

@daijizai awkwürde auch funktionieren - das gleiche Prinzip mitprintf
Roaima

Das einzige, woran ich scheitern kann, hätte ich nicht vorhersehen können, ist aber bedeutsam. Es scheint einen Backslash aus dem Textteil zu entfernen.
Daijizai

@daijizai nicht hier, tut es nicht. Wie füttert man den Text mit einem Backslash? Ich habe ein Backslashed-Beispiel für Sie hinzugefügt
Roaima

Bei meiner Verwendung mit meinem internen Datensatz gibt es Zeilen mit der Textspalte, die Zeichenfolgen wie SOME \ Text \ Might \ Be \ Here \ 4Realz enthalten. Als dieser Datensatz an die Perl-Anweisung übergeben wurde, ergab sich eine Antwort wie SOMETextMightBeHere4Realz
daijizai

3

Hier ist ein möglicher Ansatz:
sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'

Beispiele

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

Arbeiten Sie auch mit dieser Zeichenfolge:

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

... und diese Zeichenfolge:

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

Leider bricht dies zusammen, wenn die Ziffern steigen. Zum Beispiel: 1.1.11.111, Einige Texte hier wurden: 1.1.101.11001, Einige Texte hier
daijizai

@daijizai Bitte sehen Sie meine Bearbeitung. Würde dies die Anforderung erfüllen?
Maulinglawns

Leider nicht, aber ich denke, das könnte meine Schuld sein. Die Nullfüllung muss zwei bis zwei Stellen in Feld 2 und drei und drei Stellen in Feld 4 betragen. Im Wesentlichen [0-9]. [0-9] {2}. [0-9] {2}. [0 -9] {3}, einige Texte hier
daijizai

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

Erläuterung:

Die hier verwendete Methode besteht darin, die Nachbarschaften der Numerik zu betrachten und darauf basierend Maßnahmen zu ergreifen. Die 2. und 3. Zahl sehen also auf beiden Seiten einen Punkt, während die 4. Zahl links einen Punkt und rechts ein Komma sieht.

Die $ 1 wird gesetzt, wenn der Regex den Pfad der 2. oder 3. Zahl nimmt und dementsprechend die Präzisionsauffüllung 2 ist. OTOH, für die 4. Zahl ist die Auffüllung 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

Ergebnisse:

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.