替换自行结束以来的第二个点

时间:2012-08-08 20:38:45

标签: linux bash sed awk

如何更换自行尾以来的第二个点?

11.22.mail.su => 11.22@mail.su
22.mails.de => 22@mails.de

我对sedawk感兴趣的示例。

5 个答案:

答案 0 :(得分:3)

sed而言,试试这个:

sed -e 's/\.\([^.]*\.[^.]*\)$/@\1/'

所以:

# echo "11.22.mail.su" | sed -e 's/\.\([^\.]*\.[^\.]*\)$/@\1/g'
11.22@mail.su
# echo "22.mails.de" | sed -e 's/\.\([^\.]*\.[^\.]*\)$/@\1/g'
22@mails.de

答案 1 :(得分:1)

使用awk

awk '{ $0 = gensub( /\.([^.]+\.[^.]+)$/, "@\\1", 1 ); print }' infile

输出:

11.22@mail.su
22@mails.de

答案 2 :(得分:0)

这可能对您有用:

sed 's/\(.*\)\.\(.*\.\)/\1@\2/' file

答案 3 :(得分:0)

这是一个纯粹的bash解决方案(不是我建议使用它,如果需要,可以组合各个步骤):

# An extended pattern to match a single field. letters, numbers, and a hyphen
# Add characters if necessary
shopt -s extglob
field='+([[:alnum:]-])'   

for foo in 11.22.mail.su 22.mails.de; do

    # The first part: drop the last two fields and the dots that precede them
    first="${foo%.$field.$field}"

    # The first part, followed by the @, followed by the full string minus the first
    # part and its following dot.
    modified="$first@${foo/#$first.}"

done

使用bash的正则表达式支持会好一点。

for foo in 11.22.mail.su 22.mails.de; do
    [[ $foo =~ (.*)\.([^.]+\.[^.]+) ]]
    # Three ways to join the two halves with @ 
    one_way="$BASH_REMATCH[1]@${BASH_REMATCH[2]}

    printf -v second_way "%s@%s" ${BASH_REMATCH[@]:1:2}

    SAVE_IFS="$IFS"
    IFS="@"
    third_way="@{BASH_REMATCH[*]:1:2}"
    IFS="$SAVE_IFS"
done

答案 4 :(得分:0)

花了一秒钟看看你在做什么。请注意,这是一个有效的电子邮件地址:

bob@mail.server.com

这就是:

bob.smith@mail.server.com

您说从行的结尾替换第二个句点。这意味着您的正则表达式应该锚定到行尾。正则表达式末尾的$就是这样。

让我们来看看你的例子:

11.22.mail.su

您希望匹配.mail.su。让我们从最后一个字符$开始。我们可以通过.*来表示任何字符组合。这表示从零到行长度的任何字符串。句点代表任何字符,*代表前面的零个或多个。

句点是一个特殊的正则表达式字符,因此我们需要在其前面加一个反斜杠才能成为句点:\.。到目前为止一切都很好。

这应该有效:

\..*\..*$

并且,将括号括在我们想要匹配的内容中:

(\.)(.*)(\.)(.*)$

有!第一个(。)捕获第二个到最后一个周期。下一个(.*)捕获零个或多个字符,第三个捕获,(.*)捕获行的其余部分,$将其锚定在最后。

除非它不起作用,因为正则表达式是贪婪的。例如,如果我将此作为我的正则表达式:

.*###

我的字符串看起来像这样:

first###second###third###fourth

该正则表达式不捕获first###。它捕获最长的字符串,恰好是first###second###third###

解决这个问题的方法是排除你想要匹配的角色。在这种情况下,我们不希望匹配#。因此,我们可以这样做:

[^#]*###

这只会匹配first###[^#]表示除 #以外的任何字符 *表示零个或多个非#字符。所以,我要将上面表达式中的.*替换为[^.],这意味着除了句号之外的任何字符。

在:

(\.)(.*)(\.)(.*)$

后:

(\.)([^.]*)(\.)([^.]*)$

查看第二组和第四组的差异?

还有一个小问题:在sed,这就是我正在使用的,你必须在括号前放一个反斜杠,否则它们只是字符(和字符串中的)。这是你必须在前面放一个反斜杠以使其变得神奇的唯一角色。每个其他神奇的正则表达式角色都是神奇的,直到你在它前面放一个反斜杠。这意味着代替:

(\.)([^.]*)(\.)([^.]*)$

我们需要这样做:

\(\.\)\([^.]*\)\(\.\)\([^.]*\)$

与上面相同,但现在在每个开括号和右括号之前都有反斜杠。

现在,我们有一些与你的字符串结尾匹配的东西,让我们做替换。首先,一个简单的测试:

$ echo "11.22.mail.su" | sed 's/\(\.\)\([^.]*\)\(\.\)\([^.]*\)$/FOO/'
11.12FOO
是的,这与结束相符。接下来,我们可以通过在组号前加一个反斜杠来引用分组:

$ echo "11.22.mail.su" | sed 's/\(\.\)\([^.]*\)\(\.\)\([^.]*\)$/@\2\3\4/'
11.22@mail.su

完美。请注意,第一组是我的第一个时期。我用@替换它。接下来,我想保留第二,第三和第四组。因此,我的替换字符串是@\2\3\4

顺便说一下,我真的不需要四个分组。我可以简单地匹配期间,然后将其余部分作为一个组:

echo "11.22.mail.su" | sed 's/\.\([^.]*\.[^.]*\)$/@\1/'

是的,正则表达式非常简单易读!我的一个朋友将正则表达称为水手诅咒,因为在旧漫画中,当有人列出一堆粗俗时,他们会使用正则表达符号。 *

Perl的一个不错的功能是你可以在多行中分解正则表达式,这样你就可以评论正在发生的事情:

#! /usr/bin/env perl

$string = "11.22.mail.su";
$string =~ s/       #Start of my substitution
\.                  #A period
(                   #Start capturing a string
[^.]*               #Everything up to the next period.
\.                  #The next period
[^.]*)$             #And capture it to the end of the line
/@\1/x;             #Replace with a "@" and the rest of the string

print "String = '$string'\n";

$ test.pl
String = '11.22@mail.su'

关于Perl的另一个好处是括号具有特殊含义,除非你在它们前面添加反斜杠。 (与sed相反)。


我有点提到了一件事,但没有真正关注。此[^.]*匹配 或更多非期间。这可能是正则表达式的问题。要解决问题并强制至少匹配一个问题,您需要将正则表达式加倍。例如,[^#]*#FOO将与THIS IS A #FOO匹配,并且仅匹配普通#FOO

如果我执行此操作:[^#][^#]*#FOO并将正则表达式加倍,我可以保证在#之前至少有一个非#字符。该正则表达式将匹配THIS IS A #FOO,但不仅仅是普通#FOO

所以,我们可能必须从:

$ sed 's/\(\.\)\([^.]*\)\(\.\)\([^.]*\)$/FOO/'

$ sed 's/\(\.\)\([^.][^.]*\)\(\.\)\([^.][^.]*\)$/FOO/'