自一行结束以来如何替换第二个点?
11.22.mail.su => 11.22@mail.su
22.mails.de => 22@mails.de
等等
我对sed
or的示例感兴趣awk
。
至于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
使用awk
:
awk '{ $0 = gensub( /\.([^.]+\.[^.]+)$/, "@\\1", 1 ); print }' infile
输出:
11.22@mail.su
22@mails.de
这是一个纯 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
花了我一秒钟看看你在做什么。提醒一下,这是一个有效的电子邮件地址:
bob@mail.server.com
这是这样的:
bob.smith@mail.server.com
你说从行尾替换第二个句号。这意味着您的正则表达式应该锚定到行尾。$
正则表达式末尾的A就是这样做的。
让我们看一下您的示例:
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
will matchTHIS IS A #FOO
并且 will match just plain #FOO
too。
如果我这样做:[^#][^#]*#FOO
并将正则表达式加倍,我可以保证在 . 之前至少有一个非#
字符#
。该正则表达式将匹配THIS IS A #FOO
,但不只是简单#FOO
的。
所以,我们可能不得不从:
$ sed 's/\(\.\)\([^.]*\)\(\.\)\([^.]*\)$/FOO/'
至
$ sed 's/\(\.\)\([^.][^.]*\)\(\.\)\([^.][^.]*\)$/FOO/'
这可能对您有用:
sed 's/\(.*\)\.\(.*\.\)/\1@\2/' file