实现扩展的正则表达式以根据字符串中的位置添加可变数量的前导零


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中的2位数字零填充,并将字段4中的3位数字零填充,以使所有项目的标准长度为[0-9]。[0-9] { 2}。[0-9] {2}。[0-9] {3}

1.01.01.001,Some Text Here

为了我的一生,我什至无法想像如何修改边界以包括必要的参数,以便仅在一个句点之后捕捉到数字。我认为这与\ b的使用有关,我理解\ 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/.//' 但是,我很想知道是否有更优雅的方法。
daijizai

1
奇怪的是,反转字符串,应用尾随零,然后反转结果可以更轻松地实现目标。
roaima

2
使用printf(或printf在Awk中进行呼叫)可能会更简单。
通配符

1
这绝对是将来可以使用awk或perl(或其他具有printf和易于进行字段拆分的语言)的语言更容易实现,阅读,理解和修改的东西。
cas

1
@Wildcard-点好。您能指出一些有关调试sed的信息吗?通常情况下,我会诉诸于长时间的凝视,这些词语会被粗鄙的人打断。;)除非如此,我有时会把sed语句分解成较小的部分,并在重新组合之前尝试使每个部分都能正常工作。我最近读了一个很棒的教程github.com/learnbyexample/Command-line-text-processing/blob/…,并且我确定其中一些示例在我应用长时间凝视之前是错误的。

Answers:


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不会删除反斜杠。
roaima


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

具有讽刺意味的是,当您发布此信息时,我正要放弃sed并转到awk。似乎符合要求。我会检查并返回。
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上增加两位两位,在字段4上增加3位。本质上是[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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.