正则表达式分割电子邮件地址

13
我需要帮助处理php正则表达式,我想要将电子邮件地址“johndoe@example.com”拆分为“johndoe”和“@example.com”。
到目前为止,我已经有了这个:preg_match('/<?([^<]+?)@/', 'johndoe@example.com', $matches);,并且我得到了Array ( [0] => johndoe@ [1] => johndoe)
那么我需要如何改变正则表达式?

域名一行代码:$domain = substr($email, strrpos($email, '@')+1); - Kamil Kiełczewski
你是否考虑过更改你接受的答案? - Brogan
7个回答

30
$parts = explode('@', "johndoe@example.com");

$user = $parts[0];
// Stick the @ back onto the domain since it was chopped off.
$domain = "@" . $parts[1];

4
更好的方法是,list($name,$_) = explode('@',$email); $domain = '@'.$_; - http://www.ideone.com/yHlz6 - Brad Christie
1
正是我所想的。而且使用explode处理起来更加省时省力。 - Patrick
@Brad Christie,看了你的Perl风格评论后,我一度以为自己把一个Perl问题解释成了PHP问题 :) - Michael Berkowski
@Michael:只是让人们猜测。我在临时解决方案中使用 $_。无论好坏与否(并冒着可读性的风险),它都可以更快地编写变量。而且,在我的记事本获得 PHP 编码的智能感知之前,我可能会继续这样做。;p - Brad Christie
2
一个电子邮件地址可以有多个“@”符号,正如https://dev59.com/uGct5IYBdhLWcg3wF5pF中所述。 - xDaizu

9

之前的一些回答是错误的,因为一个有效的电子邮件地址实际上可以包含不止一个@符号,方法是在带引号的点分隔文本中包含它。请看以下示例:

$email = 'a."b@c".d@e.f';
echo (filter_var($email, FILTER_VALIDATE_EMAIL) ? 'V' : 'Inv'), 'alid email format.';

有效的电子邮件格式。


可以存在多个分隔块的文本和大量的@符号。 这两个例子都是有效的电子邮件地址:

$email = 'a."b@c".d."@".e.f@g.h';
$email = '/."@@@@@@"./@a.b';

基于Michael Berkowski的explode答案,这个电子邮件地址看起来像这样:
$email = 'a."b@c".d@e.f';
$parts = explode('@', $email);
$user = $parts[0];
$domain = '@' . $parts[1];

用户:a."b"
域名:@c".d


任何使用此解决方案的人都应当注意潜在的滥用问题。仅仅依据这些输出结果接受一个电子邮件地址,随后将 $email 插入到数据库中可能会产生负面影响。

$email = 'a."b@c".d@INSERT BAD STUFF HERE';

只有在首先使用filter_var进行验证时,这些函数的内容才是准确的。

从左边开始:

这里提供了一个简单的非正则表达式、非拆分解决方案,用于查找不包含定界和引用文本的第一个@。基于filter_var,嵌套的定界文本被认为是无效的,因此查找正确的@是一项非常简单的搜索任务。

if(filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $a = '"';
    $b = '.';
    $c = '@';
    $d = strlen($email);
    $contained = false;
    for($i = 0; $i < $d; ++$i) {
        if($contained) {
            if($email[$i] === $a && $email[$i + 1] === $b) {
                $contained = false;
                ++$i;
            }
        }
        elseif($email[$i] === $c)
            break;
        elseif($email[$i] === $b && $email[$i + 1] === $a) {
            $contained = true;
            ++$i;
        }
    }
    $local = substr($email, 0, $i);
    $domain = substr($email, $i);
}

这里是同样的代码被封装在一个函数内。

function parse_email($email) {
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
    $a = '"';
    $b = '.';
    $c = '@';
    $d = strlen($email);
    $contained = false;
    for($i = 0; $i < $d; ++$i) {
        if($contained) {
            if($email[$i] === $a && $email[$i + 1] === $b) {
                $contained = false;
                ++$i;
            }
        }
        elseif($email[$i] === $c)
            break;
        elseif($email[$i] === $b && $email[$i + 1] === $a) {
            $contained = true;
            ++$i;
        }
    }
    return array('local' => substr($email, 0, $i), 'domain' => substr($email, $i));
}

使用中:

$email = 'a."b@c".x."@".d.e@f.g';
$email = parse_email($email);
if($email !== false)
    print_r($email);
else
    echo 'Bad email address.';

数组([local] => a."b@c".x."@".d.e [domain] => @f.g)
$email = 'a."b@c".x."@".d.e@f.g@';
$email = parse_email($email);
if($email !== false)
    print_r($email);
else
    echo 'Bad email address.';

错误的电子邮件地址。


来自右侧:

在对 filter_var 进行一些测试并研究什么是有效的域名(由点分隔的主机名)后,我创建了这个函数以获得更好的性能。在有效的电子邮件地址中,最后一个 @ 应该是真正的 @,因为 @ 符号不应出现在有效电子邮件地址的域中。

if(filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $domain = strrpos($email, '@');
    $local = substr($email, 0, $domain);
    $domain = substr($email, $domain);
}

作为一个函数:
function parse_email($email) {
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
    $a = strrpos($email, '@');
    return array('local' => substr($email, 0, $a), 'domain' => substr($email, $a));
}

或者使用explode和implode函数:

if(filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $local = explode('@', $email);
    $domain = '@' . array_pop($local);
    $local = implode('@', $local);
}

作为一个函数:

function parse_email($email) {
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
    $email = explode('@', $email);
    $domain = '@' . array_pop($email);
    return array('local' => implode('@', $email), 'domain' => $domain);
}

如果您仍想使用正则表达式,从有效电子邮件地址的结尾开始拆分字符串是最安全的选项。
/(.*)(@.*)$/

(.*) 可以匹配任何内容。
(@.*) 可以匹配以@符号开头的任何内容。
$ 表示字符串的结尾。

if(filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $local = preg_split('/(.*)(@.*)$/', $email, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    $domain = $local[1];
    $local = $local[0];
}

作为一个函数:

function parse_email($email) {
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
    $email = preg_split('/(.*)(@.*)$/', $email, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    return array('local' => $email[0], 'domain' => $email[1]);
}

或者

if(filter_var($email, FILTER_VALIDATE_EMAIL)) {
    preg_match('/(.*)(@.*)$/', $email, $matches);
    $local = $matches[1];
    $domain = $matches[2];
}

作为一个函数:

function parse_email($email) {
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
    preg_match('/(.*)(@.*)$/', $email, $matches);
    return array('local' => $matches[1], 'domain' => $matches[2]);
}

1
我从未见过实际使用多个@符号的电子邮件地址。我的原始电子邮件地址中有一个#符号,用于表示路由信息,当我阅读有关电子邮件的RFC时,我感到惊讶,因为我发现了什么是有效的和什么是无效的。尽管如此,仍然有一些被称为有效的东西,但我从未在生产中见过。 - frumbert

3
使用explode可能是在此处的最佳方法,但是如果要使用正则表达式来执行,则可以按照以下方式进行操作:
/^([^@]*)(@.*)/

^ 字符串的开头

([^@]*) 除了@符号以外的所有内容($matches[0])

(@.*) @符号后面的任何东西($matches[1])


2

答案

$parts = explode("@", $email);
$domain = array_pop($parts);
$name = implode("@",$parts);

这解决了Brogan的两个边界情况(a."b@c".d."@".e.f@g.h/."@@@@@@"./@a.b),你可以在这个Ideone链接中看到。


目前被接受的答案不正确,因为它无法处理多个 "@" 的情况。

我很喜欢@Brogan的答案,但最后一句话让我失望:

在有效的电子邮件地址中,最后的@应该是真正的@,因为@符号永远不应出现在有效电子邮件地址的域中。

这个答案支持这种说法。如果这是真的,他的答案似乎过于复杂了。


1
为什么会有“踩”?我不知道我的解决方案哪里出了问题……就我所知,它是有效的。如果不行,请告诉我原因!D: - xDaizu

0
使用正则表达式。例如:
$mailadress = "email@company.com";     
$exp_arr= preg_match_all("/(.*)@(.*)\.(.*)/",$mailadress,$newarr, PREG_SET_ORDER); 

/*
Array output:
Array
(
    [0] => Array
        (
            [0] => email@company.com
            [1] => email
            [2] => company
            [3] => com
        )

)
*/

这个匹配 @@@@@@@@@. 是无效的,但是 me@localhost 是有效的。 - Toto

0
如果你想要一个 preg_match 的解决方案,你也可以像这样做。
preg_match('/([^<]+)(@[^<]+)/','johndoe@example.com',$matches);

-1
我已经为此创建了一个通用的正则表达式,验证并创建了完整电子邮件、用户和域的命名捕获。

正则表达式:

(?<email>(?<mailbox>(?:\w|[!#$%&'*+/=?^`{|}~-])+(?:\.(?:\w|[!#$%&'*+/=?^`{|}~-])+)*)@(?<full_domain>(?<subdomains>(?:(?:[^\W\d_](?:(?:[^\W_]|-)+[^\W_])?)\.)*)(?<root_domain>[^\W\d_](?:(?:[^\W_]|-)+[^\W_])?)\.(?<tld>[^\W\d_](?:(?:[^\W_]|-)+[^\W_])?)))

解释:

(?<email>                          #  start Full Email capture
  (?<mailbox>                      #    Mailbox
    (?:\w|[!#$%&'*+/=?^`{|}~-])+   #      letter, number, underscore, or any of these special characters
    (?:                            #      Group: allow . in the middle of mailbox; can have multiple but can't be consecutive (no john..smith)
      \.                           #        match "." 
      (?:\w|[!#$%&'*+/=?^`{|}~-])+ #        letter, number, underscore, or any of these special characters
    )*                             #      allow one letter mailboxes
  )                                #    close Mailbox capture
  @                                #    match "@"
  (?<full_domain>                  #    Full Domain (including subdomains and tld)
    (?<subdomains>                 #      All Subdomains
      (?:                          #        label + '.' (so we can allow 0 or more)
        (?:                        #          label text
          [^\W\d_]                 #            start with a letter (\W is the inverse of \w so we end up with \w minus numbers and _)
          (?:                      #            paired with a ? to allow single letter domains
            (?:[^\W_]|-)+          #              allow letters, numbers, hyphens, but not underscore
            [^\W_]                 #              if domain is more than one character, it has to end with a letter or digit (not a hyphen or underscore)
          )?                       #            allow one letter sub domains
        )                          #          end label text
      \.)*                         #        allow 0 or more subdomains separated by '.'
    )                              #      close All Subdomains capture
    (?<root_domain>                #      Root Domain
      [^\W\d_]                     #        start with a letter
      (?:                          #        paired with ? to make characters after the first optional
        (?:[^\W_]|-)+              #          allow letters, numbers, hyphens
        [^\W_]                     #          if domain is more than one character, it has to end with a letter or digit (not a hyphen or underscore)
      )?                           #        allow one letter domains
    )                              #      close Root Domain capture
    \.                             #      separator
    (?<tld>                        #      TLD
      [^\W\d_]                     #        start with a letter
      (?:                          #        paired with ? to make characters after the first optional
        (?:[^\W_]|-)+              #          allow letters, numbers, hyphens
        [^\W_]                     #          if domain is more than one character, it has to end with a letter or digit (not a hyphen)
      )?                           #        allow single letter tld
    )                              #      close TLD capture
  )                                #    close Full Domain capture
)                                  #  close Full Email capture

笔记

通用正则表达式:我仅发布了正则表达式搜索本身,而不是 PHP 的独占内容。这样可以让其他人更容易使用,以“Regex Split Email Address”为名称进行查找。

功能兼容性:并非所有正则表达式处理器都支持命名捕获。如果您遇到问题,请在 Regexr 上测试您的文本(检查详细信息以查看捕获)。如果在那里运行,则请再次检查您正在使用的正则表达式引擎是否支持命名捕获。

域 RFC:域部分也基于 域 RFC 而非仅基于 2822。

危险字符: 我明确包括了 '$! 等字符,既是为了明确这些字符受到 电邮 RFC 批准,也是为了方便删除特定字符集(例如阻止可能的 SQL 注入攻击)应该在您的系统中被禁止。

无法逃脱:对于邮箱名称,我仅包含点-原子格式,有意地排除了点或斜杠转义支持。

微妙的字母:对于某些部分,我使用了[^\W\d_]而不是[a-zA-Z],以提高对英语以外语言的支持。

越界:由于某些系统中捕获组处理的特殊性,我使用+代替{,61}。如果您在某个可能受到缓冲区溢出攻击的地方使用它,请记得限制您的输入

致谢:修改自Tripleaxis社区帖子,该帖子又取自.net帮助文件。


注意:-1是来自另一个回答者,他对我为什么包括'$!感到困惑。我已经更新了答案,以更清楚地解释它们的存在。如果您发现它有用,请随意查看并评价自己。 - Chris Rudd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接