Реализация расширенного регулярного выражения для добавления переменного числа ведущих нулей в зависимости от положения в строке


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 цифр в полях 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

Это предрешенный вывод, что мне нужно по-настоящему изучить sed и все его сложности. Я работаю над этим, но ожидаю, что это конкретное заявление будет еще некоторое время доставлять мне неприятности. Любая помощь будет принята с благодарностью.

РЕДАКТИРОВАТЬ: Я нашел способ ... Это утверждение, кажется, делает то, что я ищу, но должен быть более элегантный способ сделать это.

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/.//' однако, я хотел бы знать, есть ли более элегантный подход.
Дайджизай

1
Как ни странно, если перевернуть строку, применить конечные нули, а затем изменить результат, вам будет легче достичь вашей цели.
Ройма

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. является sed решением, а 2. производит правильный вывод без расторжения текста. Отмечается как ответ. Спасибо! :-)
daijizai

@daijizai, как я уже продемонстрировал, perlверсия не удаляет обратную косую черту.
Ройма

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
Или авк. Но +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

По иронии судьбы, я как раз собирался сдаться в седе и перейти к awk, когда вы это опубликовали. Кажется, это отвечает всем требованиям. Я проверю это и вернусь.
Дайджизай

@daijizai awkбудет работать тоже - тот же принцип, используяprintf
roaima

Единственное, чего не получается, я не мог предвидеть, но это важно. Кажется, убрать обратную косую черту из текстовой части.
Дайджизай

@ 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}, немного текста здесь
дайджизай

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.