Реалізація розширеного регулярного вираження для додавання змінної кількості провідних нулів на основі позиції в рядку


10

У мене виникають проблеми зі скороченням синтаксису sed, щоб додати різну кількість провідних нулів до числової організаційної схеми. Струни, над якими я оперую, виглядають як

1.1.1.1,Some Text Here

використання синтаксису sed

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

Я в змозі отримати відповідь

01.01.01.01,Some Text Here

Однак те, що я шукаю, - це те, що нульове заповнення до двох цифр у полях 2 та 3 та 3 цифр у полі 4, щоб усі елементи були стандартної довжини у [0-9]. [0-9] { 2}. [0-9] {2}. [0-9] {3}

1.01.01.001,Some Text Here

Протягом життя я не можу зрозуміти навіть, як змінити межу, щоб включити параметри, необхідні для оснащення лише цифрами після певного періоду. Я думаю, що це має щось спільне з використанням \ b, яке я розумію, відповідає нульовим символам на межі слова, але я не розумію, чому мої спроби додати період до відповідності провалюються так:

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

Крім того, я очікую, що у мене виникнуть додаткові проблеми, якщо виписка містить такий текст, як:

1.1.1.1,Some Number 1 Here

Це заздалегідь зроблений висновок, що мені потрібно справді вивчити сед та всі його складності. Я працюю над цим, але сподіваюся, що ця конкретна заява на деякий час буде створювати мені неприємності. Будь-яка допомога буде дуже вдячна.

EDIT: Я вигадав спосіб ... Це твердження, здається, робить те, що я шукаю, але повинен бути більш елегантний спосіб зробити це.

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

Також синтаксично це спричинить проблеми, якщо в тексті з'явиться аналогічний формат чисел ... подібний до:

1.1.1.1,Some Text Referring to Document XXX Heading 1.2.3

У такому випадку це призведе до:

1.01.01.001,Some Text Referring to Document XXX Heading 01.02.03

Вирішено Дякую всім за допомогу тут. Я спочатку вирішив проблему з відповіддю, який я прийняв нижче. Я відчуваю, що рішення перенесло на Python як частину більшого рішення, що використовує тип нижче:

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

s=sorted(reader, key=getPaddedKey)

Здається, це те, що я шукаю: sed -r ':r;s/\b[0-9]{1,1}\.\b/0&/;tr;:i;s/\b[0-9]{1,2},\b/0&/;ti;s/.//' однак, я хотів би знати, чи є більш елегантний підхід.
daijizai

1
Як не дивно, обернення рядка, застосування задніх нулів, а потім повернення результату може досягти вашої мети легше.
roaima

2
Використання printf(або printfдзвінок у Awk) може бути більш простим.
Wildcard

1
це, безумовно, щось, що буде легше реалізувати, прочитати, зрозуміти та змінити в майбутньому на мові, наприклад, awk або perl (або будь-що інше, що має printf та легко розбиває поле).
cas

1
@Wildcard - точка добре взята. Чи можете ви вказати мені щось про налагодження sed? Зазвичай я вдаюсь до тривалого вдивляння, перемежованого експлікаціями. ;) Заперечуючи це, я іноді розбиваю заяву sed на більш дрібні шматки і намагаюся змусити кожного працювати, перш ніж їх знову поєднувати. Нещодавно я прочитав чудовий підручник github.com/learnbyexample/Command-line-text-processing/blob/…, і я був впевнений, що деякі приклади помиляються, поки я не застосував тривалий погляд.
Джо

Відповіді:


4

Використання: 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"

Пояснення:

  1. Перша підстановка додає певну кількість нулів до кожного числа. 1 нуль до 2 і 3 числа, 2 нуль до 4 числа. Не має значення, скільки цифр вже є.
  2. Друга субституція видаляє всі зайві нулі, залишаючи лише необхідну кількість чисел. 2 і 3 числа повинні містити лише 2 цифри. Залишає їх і знімає залишки. Четверте число повинно містити лише 3 цифри. Залишає їх і знімає залишки.

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

Хоча в кінцевому підсумку я щойно закінчив написання цього сценарію в Python для доцільності, це найкраща відповідь на моє запитання, як написано, враховуючи, що раніше представлений Perl зняв зворотні риски (принаймні) з результату. Цей 1. - це розчин, і 2. дає належний вихід без домагання тексту. Позначення як відповідь. Дякую! :-)
daijizai

@daijizai, як я вже продемонстрував, perlверсія не знімає відхилень.
roaima

9

Баш може впоратися з цим. Це буде набагато повільніше, ніж 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

2
Або Awk. Але +1 для використання printf, розумний інструмент. (Awk printfтакож є і краще розроблений, ніж bashдля обробки тексту.) Також див. Чому використання циклу оболонки для обробки тексту вважається поганою практикою?
Wildcard

5

Ви спеціально не просили perlрішення, але ось все одно. Особисто я вважаю, що це трохи простіше читати, особливо коли їх розбивають на кілька рядків.

По-перше, ось однолінійний:

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

Її результати:

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

І ось perlскрипт розбитий та прокоментований ( -nпрапор ставить неявний while read; do ... doneцикл навколо коду):

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

За іронією долі, я збирався просто відмовитись від седу і перейти до пробудження, коли ви розмістили це. Здається, підходить до законопроекту. Я перевірю це і повернусь.
daijizai

@daijizai також awkбуде працювати - той самий принцип, використовуючиprintf
roaima

Єдине, чого я не міг передбачити, але це важливо. Здається, зніміть нахил від текстової частини.
daijizai

@daijizai тут не так. Як ви подаєте текст із зворотним нахилом? Я додав для вас зворотний приклад
roaima

У моєму використанні з моїм внутрішнім набором даних є рядки з текстовим стовпцем, що містить рядки типу SOME \ Text \ Might \ Be \ Here \ 4Realz. Коли цей набір даних передано до заяви perl, це призвело до такої відповіді, як SOMETextMightBeHere4Realz
daijizai

3

Ось один з можливих підходів:
sed -E 's/([0-9]*\.)/0\1/g;s/.//;s/([0-9]*,)/00\1/'

Приклади

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

Також працюйте з цим рядком:

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

... і цей рядок:

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

На жаль, це руйнується, коли цифри піднімаються. Наприклад: 1.1.11.111, Деякі тексти тут Стали: 1.1.101.11001, Деякі тексти тут
daijizai

@daijizai Перегляньте мою редакцію. Чи відповідає це вимозі?
maulinglawns

На жаль, ні, але я думаю, що це моя вина. Поле нульового заповнення має бути двома двозначними на полях 2 та 3 та 3 цифрами у полі 4. По суті [0-9]. [0-9] {2}. [0-9] {2}. [0 -9] {3}, деякий текст тут
daijizai

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

Пояснення:

Метод, який використовується тут, полягає в тому, щоб переглянути околиці чисел та вжити заходів на основі цього. Отже, 2-е і 3-е числа бачать крапку з обох сторін, а 4-е число - на лівій крапці, а праворуч - кома.

$ 1 встановлюється, коли регулярний вираз проходить шлях 2-го або 3-го числа, і відповідно точність прокладки становить 2. OTOH, для 4-го числа, прокладка дорівнює 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

Результати:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.