Implementowanie rozszerzonego wyrażenia regularnego w celu dodania zmiennej liczby wiodących zer na podstawie pozycji w ciągu

10

Mam problem z obniżeniem składni sed, aby dodać różną liczbę zer wiodących do numerycznego schematu organizacyjnego. Wyglądają na ciągi, na których operuję

1.1.1.1,Some Text Here

wykorzystując składnię sed

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

Jestem w stanie uzyskać odpowiedź

01.01.01.01,Some Text Here

Jednak to, czego szukam, to wypełnienie zera do 2 cyfr w polach 2 i 3 i 3 cyfr w polu 4, aby wszystkie elementy miały standardową długość w [0-9]. [0-9] { 2}. [0–9] {2}. [0–9] {3}

1.01.01.001,Some Text Here

Przez całe życie nie potrafię nawet wymyślić, jak zmodyfikować granicę, aby uwzględnić parametry niezbędne do przyciągania tylko cyfr po kropce. Myślę, że ma to coś wspólnego z użyciem \ b, które, jak rozumiem, dopasowuje zero znaków na granicy słów, ale nie rozumiem, dlaczego moje próby dodania kropki do dopasowania kończą się następująco:

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

Ponadto oczekuję, że będę mieć dodatkowe problemy, jeśli instrukcja zawiera tekst taki jak:

1.1.1.1,Some Number 1 Here

Jest to przesądzony wniosek, że muszę naprawdę nauczyć się sed i wszystkich jego zawiłości. Pracuję nad tym, ale spodziewam się, że to konkretne stwierdzenie będzie nadal przysparzało mi kłopotów. Każda pomoc byłaby bardzo mile widziana.

EDYCJA: Wymyśliłem sposób ... To stwierdzenie wydaje się robić to, czego szukam, ale musi być bardziej elegancki sposób na zrobienie tego.

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

Również syntaktycznie spowoduje to problemy, jeśli w tekście pojawi się podobny format liczb ... podobny do:

1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3

W takim przypadku spowoduje to:

1.01.01.001,Some Text Referring to Document XXX Heading 01.02.03

Rozwiązany Dziękuję wszystkim za pomoc tutaj. Początkowo rozwiązałem problem z odpowiedzią, którą zaakceptowałem poniżej. Wydaje mi się, że przeniosłem rozwiązanie do Pythona jako część większego rozwiązania wykorzystującego poniższy sposób:

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
źródło
Wydaje się, że robi to, czego szukam: sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//' chciałbym jednak wiedzieć, czy istnieje bardziej eleganckie podejście.
daijizai
1
O dziwo, odwrócenie łańcucha, zastosowanie zer końcowych , a następnie odwrócenie wyniku może łatwiej osiągnąć cel.
roaima
2
Korzystanie printf(lub printfpołączenie w ramach Awk) może być prostsze.
Wildcard
1
jest to zdecydowanie coś, co będzie łatwiejsze do wdrożenia, czytania, zrozumienia i modyfikacji w przyszłości w języku takim jak awk lub perl (lub cokolwiek innego, co ma printf i łatwe dzielenie pól).
cas
1
@Wildcard - punkt dobrze zajęty. Czy możesz wskazać mi coś na temat debugowania sed? Zwykle uciekam się do długiego patrzenia przerywanego przekleństwami. ;) Nie licząc tego, czasami dzielę oświadczenie sed na mniejsze części i próbuję zmusić każdą z nich do pracy przed ponownym połączeniem. Niedawno przeczytałem świetny samouczek github.com/learnbyexample/Command-line-text-processing/blob/... i byłem pewien, że niektóre przykłady były błędne, dopóki nie zastosowałem długotrwałego patrzenia.
Joe

Odpowiedzi:

4

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

Wyjaśnienie:

  1. Pierwsza substytucja dodaje określoną liczbę zer do każdej liczby. 1 zera na 2 i 3 liczby, 2 zera na 4 cyfry. Nie ma znaczenia, ile już jest cyfr.
  2. Druga podstawienie usuwa wszystkie dodatkowe zera, pozostawiając tylko potrzebną liczbę liczb. Liczby 2 i 3 powinny zawierać tylko 2 cyfry. Pozostawia je i usuwa resztki. Czwarta liczba powinna zawierać tylko 3 cyfry. Pozostawia je i usuwa resztki.

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
źródło
Chociaż ostatecznie skończyłem ze skryptowaniem tego w Pythonie dla wygody, jest to najlepsza odpowiedź na moje pytanie w formie pisemnej, biorąc pod uwagę, że wcześniej przesłany perl usunął ukośniki odwrotne (przynajmniej) z danych wyjściowych. To 1. jest rozwiązaniem sed, a 2. zapewnia prawidłowe wyjście bez molestowania tekstu. Oznaczenie jako odpowiedź. Dzięki! :-)
daijizai
@daijizai, jak już wykazałem, perlwersja nie usuwa ukośników odwrotnych.
roaima
9

bash sobie z tym poradzi. Będzie jednak dużo wolniejszy niż 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
źródło
2
Lub Awk. Ale +1 za użycie printfsensownego narzędzia. (Awk ma printfrównież i jest lepiej zaprojektowany niż bashdo przetwarzania tekstu.) Zobacz także Dlaczego używanie pętli powłoki do przetwarzania tekstu jest uważane za złą praktykę?
Wildcard
5

Nie poprosiłeś konkretnie o perlrozwiązanie, ale i tak jest jedno. Osobiście uważam, że jest to trochę łatwiejsze do odczytania, zwłaszcza gdy jest podzielone na kilka wierszy.

Po pierwsze, jest to jedna linijka:

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

Jego wyniki:

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

A oto perlskrypt podzielony i skomentowany ( -nflaga umieszcza niejawną while read; do ... donepętlę wokół kodu):

($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
źródło
Jak na ironię, właśnie zamierzałem zrezygnować z sed i przejść do awk, kiedy to opublikowałeś. Wygląda na to, że pasuje do rachunku. Sprawdzę to i wrócę.
daijizai
@daijizai też awkdziałałby - przy użyciu tej samej zasadyprintf
roaima
Jedyne, co się nie udaje, nie mogłem się tego spodziewać, ale jest znaczące. Wydaje się, że usuwa odwrotny ukośnik z części tekstowej.
daijizai
@ Daijizai nie tutaj nie ma. Jak podajesz tekst odwrotnym ukośnikiem? Dodałem dla ciebie przykład z ukośnikiem
roaima
W moim zastosowaniu z moim wewnętrznym zestawem danych są wiersze z kolumną tekstową zawierającą ciągi takie jak SOME \ Text \ Might \ Be \ Here \ 4Realz. Gdy ten zestaw danych został przekazany do instrukcji perl, wywołał on odpowiedź taką jak SOMETextMightBeHere4Realz
daijizai
3

Oto jedno z możliwych podejść:
sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'

Przykłady

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

Pracuj także z tym ciągiem:

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

... i ten ciąg:

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
źródło
Niestety rozkłada się to wraz ze wzrostem liczby. Na przykład: 1.1.11.111, jakiś tekst tutaj stał: 1.1.101.11001, jakiś tekst tutaj
daijizai
@daijizai Proszę zobaczyć moją edycję. Czy to spełni wymagania?
maulinglawns 18.07.17
Niestety nie, ale myślę, że to może być moja wina. Uzupełnianie zerowe musi wynosić maksymalnie dwie dwie cyfry w polu 2 i 3 oraz 3 cyfry w polu 4. Zasadniczo [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'

Wyjaśnienie:

Zastosowana metoda polega na tym, by spojrzeć na dzielnice numeryczne i na tej podstawie podjąć działania. Tak więc, druga i trzecia liczba widzą kropkę po obu stronach, podczas gdy czwarta cyfra widzi kropkę po jej lewej stronie i przecinek po prawej.

1 $ jest ustawiany, gdy wyrażenie regularne przyjmuje ścieżkę 2-ej lub 3-tej liczby i odpowiednio dopełnienie precyzyjne wynosi 2. OTOH, dla 4-tej liczby wypełnienie wynosi 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

Wyniki:

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

źródło