【转】多语言的正则表达式,咱们应该掌握

正则表达式,软件工程中最为强大,且普遍适用,使人信服的技术之一。从验证电子邮件地址到执行复杂的代码重构器,正则表达式的用途很是普遍,是任何软件工程师工具箱中必不可少的条目。php

什么是正则表达式?

正则表达式(或Regex,或Regexp)是使用字符序列描述复杂搜索模式的一种方式。

然而,专门的Regex语法因为其复杂性使得有些表达式变得不可访问。例如,下面的这个基本的正则表达式,它表示24小时制HH / MM格式的时间。

\b([01]?[0-9]|2[0-3]):([0-5]\d)\b

若是你以为这看上去略显复杂,别担忧,当咱们完成这个教程时,理解这个表达式将会是小菜一碟。
Learn once, write anywhere

几乎任何编程语言均可以使用Regex。Regex的知识对于验证用户输入,与Unix shell进行交互,在你喜欢的文本编辑器中搜索/重构代码,执行数据库文本搜索等等都很是有用。

在本教程中,我将尝试在各类场景、语言和环境中对Regex的语法和使用进行简明易懂的介绍。

此Web应用程序是我用于构建、测试和调试Regex最喜欢的工具。我强烈推荐你们使用它来测试咱们将在本教程中介绍的表达式。

本教程中的示例源代码能够在Github存储库中找到——https://github.com/triestpa/You-Should-Learn-Regex
0 – 匹配任何数字行

咱们将从一个很是简单的例子开始——匹配任何只包含数字的行。

^[0-9]+$

让咱们一点一点的解释吧。

    ^ ——表示一行的开始。
    [0-9] ——匹配0到9之间的数字
    + ——匹配前一个表达式的一个或多个实例。
    $ ——表示行尾。

咱们能够用伪英文重写这个Regex为[start of line][one or more digits][end of line]。

很简单,不是吗?

    咱们能够用\d替换[0-9],结果相同(匹配全部数字)。

这个表达式(和通常的正则表达式)的伟大之处在于它无需太多修改,就能够用到任何编程语言中。

为了演示,咱们先快速了解如何使用16种最受欢迎的编程语言对文本文件执行此简单的Regex搜索。

咱们使用如下输入文件(test.txt)为例。

1234
abcde
12db2
5362

1

每一个脚本都将使用这个正则表达式读取并搜索test.txt文件,并将结果('1234', '5362', '1')输出到控制台。
语言范例
0.0 – Javascript / Node.js / Typescript
const fs = require('fs')
const testFile = fs.readFileSync('test.txt', 'utf8')
const regex = /^([0-9]+)$/gm
let results = testFile.match(regex)
console.log(results)

0.1 – Python
import re

with open('test.txt', 'r') as f:
  test_string = f.read()
  regex = re.compile(r'^([0-9]+)$', re.MULTILINE)
  result = regex.findall(test_string)
  print(result)

0.2 – R
fileLines <- readLines("test.txt")
results <- grep("^[0-9]+$", fileLines, value = TRUE)
print (results)

0.3 – Ruby
File.open("test.txt", "rb") do |f|
    test_str = f.read
    re = /^[0-9]+$/m
    test_str.scan(re) do |match|
        puts match.to_s
    end
endhtml


0.4 – Haskell
import Text.Regex.PCRE

main = do
  fileContents <- readFile "test.txt"
  let stringResult = fileContents =~ "^[0-9]+$" :: AllTextMatches [] String
  print (getAllTextMatches stringResult)

0.5 – Perl
open my $fh, '<', 'test.txt' or die "Unable to open file $!";
read $fh, my $file_content, -s $fh;
close $fh;
my $regex = qr/^([0-9]+)$/mp;
my @matches = $file_content =~ /$regex/g;
print join(',', @matches);

0.6 – PHP
<?php
$myfile = fopen("test.txt", "r") or die("Unable to open file.");
$test_str = fread($myfile,filesize("test.txt"));
fclose($myfile);
$re = '/^[0-9]+$/m';
preg_match_all($re, $test_str, $matches, PREG_SET_ORDER, 0);
var_dump($matches);
?>

0.7 – Go
package main

import (
    "fmt"
    "io/ioutil"
    "regexp"
)

func main() {
    testFile, err := ioutil.ReadFile("test.txt")
    if err != nil { fmt.Print(err) }
    testString := string(testFile)
    var re = regexp.MustCompile(`(?m)^([0-9]+)$`)
    var results = re.FindAllString(testString, -1)
    fmt.Println(results)
}

0.8 – Java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;

class FileRegexExample {
  public static void main(String[] args) {
    try {
      String content = new String(Files.readAllBytes(Paths.get("test.txt")));
      Pattern pattern = Pattern.compile("^[0-9]+$", Pattern.MULTILINE);
      Matcher matcher = pattern.matcher(content);
      ArrayList<String> matchList = new ArrayList<String>();

      while (matcher.find()) {
        matchList.add(matcher.group());
      }

      System.out.println(matchList);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

0.9 – Kotlin
import java.io.File
import kotlin.text.Regex
import kotlin.text.RegexOption

val file = File("test.txt")
val content:String = file.readText()
val regex = Regex("^[0-9]+$", RegexOption.MULTILINE)
val results = regex.findAll(content).map{ result -> result.value }.toList()
println(results)

0.10 – Scala
import scala.io.Source
import scala.util.matching.Regex

object FileRegexExample {
  def main(args: Array[String]) {
    val fileContents = Source.fromFile("test.txt").getLines.mkString("\n")
    val pattern = "(?m)^[0-9]+$".r
    val results = (pattern findAllIn fileContents).mkString(",")
    println(results)
  }
}

0.11 – Swift
import Cocoa
do {
    let fileText = try String(contentsOfFile: "test.txt", encoding: String.Encoding.utf8)
    let regex = try! NSRegularExpression(pattern: "^[0-9]+$", options: [ .anchorsMatchLines ])
    let results = regex.matches(in: fileText, options: [], range: NSRange(location: 0, length: fileText.characters.count))
    let matches = results.map { String(fileText[Range($0.range, in: fileText)!]) }
    print(matches)
} catch {
    print(error)
}

0.12 – Rust
extern crate regex;
use std::fs::File;
use std::io::prelude::*;
use regex::Regex;

fn main() {
  let mut f = File::open("test.txt").expect("file not found");
  let mut test_str = String::new();
  f.read_to_string(&mut test_str).expect("something went wrong reading the file");

  let regex = match Regex::new(r"(?m)^([0-9]+)$") {
    Ok(r) => r,
    Err(e) => {
      println!("Could not compile regex: {}", e);
      return;
    }
  };

  let result = regex.find_iter(&test_str);
  for mat in result {
    println!("{}", &test_str[mat.start()..mat.end()]);
  }
}

0.13 – C#
using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;

namespace RegexExample
{
    class FileRegexExample
    {
        static void Main()
        {
            string text = File.ReadAllText(@"./test.txt", Encoding.UTF8);
            Regex regex = new Regex("^[0-9]+$", RegexOptions.Multiline);
            MatchCollection mc = regex.Matches(text);
            var matches = mc.OfType<Match>().Select(m => m.Value).ToArray();
            Console.WriteLine(string.Join(" ", matches));
        }
    }
}

0.14 – C++
#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <regex>
using namespace std;

int main () {
  ifstream t("test.txt");
  stringstream buffer;
  buffer << t.rdbuf();
  string testString = buffer.str();

  regex numberLineRegex("(^|\n)([0-9]+)($|\n)");
  sregex_iterator it(testString.begin(), testString.end(), numberLineRegex);
  sregex_iterator it_end;

  while(it != it_end) {
    cout << it -> str();
    ++it;
  }
}

0.15 – Bash
#!bin/bash
grep -E '^[0-9]+$' test.txt

以十六种语言编写出相同的操做是一个有趣的练习,可是,接下来在本教程中,咱们将主要使用Javascript和Python(最后还有一点Bash),由于这些语言(在我看来)倾向于产生最清晰和更可读的实现。
1 – 年份匹配

咱们来看看另一个简单的例子——匹配二十或二十一世纪中任何有效的一年。

\b(19|20)\d{2}\b

咱们使用\b而不是^和$来开始和结束这个正则表达式。\b表示单词边界,或两个单词之间的空格。这容许咱们在文本块(而不是代码行)中匹配年份,这对于搜索如段落文本很是有用。

    \b ——字边界
    (19|20) ——使用或(|)操做数匹配’19′或’20′。
    \d{2}——两位数,与[0-9]{2}相同
    \b ——字边界

    请注意\b不一样于\s,\s是用于空格字符的代码。\b搜索一个单词字符前面或者后面没有另外一个字符的地方,所以它搜索单词字符的缺失,而\s明确搜索空格字符。\b特别适用于咱们想要匹配特定序列/单词的状况,而不是特定序列/单词以前或以后有空格的状况。

1.0 – 真实示例 – 计数年份
咱们能够在Python脚本中使用此表达式来查找维基百科历史部分的文章中说起20或21世纪内年份的次数。

import re
import urllib.request
import operator

# Download wiki page
url = "https://en.wikipedia.org/wiki/Diplomatic_history_of_World_War_II"
html = urllib.request.urlopen(url).read()

# Find all mentioned years in the 20th or 21st century
regex = r"\b(?:19|20)\d{2}\b"
matches = re.findall(regex, str(html))

# Form a dict of the number of occurrences of each year
year_counts = dict((year, matches.count(year)) for year in set(matches))

# Print the dict sorted in descending order
for year in sorted(year_counts, key=year_counts.get, reverse=True):
  print(year, year_counts[year])

上述脚本将按照说起的次数依次打印年份。

1941 137
1943 80
1940 76
1945 73
1939 71
...

2 – 匹配时间
如今咱们要定义一个正则表达式来匹配24小时格式(MM:HH,如16:59)的任什么时候间。

\b([01]?[0-9]|2[0-3]):([0-5]\d)\b

    \b——字边界
    [01]——0或1
    ?——表示上述模式是可选的。
    [0-9]—— 0到9之间的任何数字
    |——OR操做数
    2[0-3]——2,后面跟0和3之间的任何数字(即20-23)
    :——匹配:字符
    [0-5]——0到5之间的任何数字
    \d——0到9之间的任何数字(与[0-9]相同)
    \b ——字边界

2.0 – 捕获组
你可能已经注意到上述模式中有了新内容—— 咱们在括号 ( ... )中封装小时和分钟的捕获片断。这容许咱们将模式的每一个部分定义为捕获组。

捕获组容许咱们单独提取、转换和从新排列每一个匹配模式的片断。
2.1 – 真实示例 – 时间分析
例如,在上述24小时模式中,咱们定义了两个捕获组—— 时和分。

咱们能够轻松地提取这些捕获组。

如下是咱们如何使用Javascript将24小时制的时间分解成小时和分钟。

const regex = /\b([01]?[0-9]|2[0-3]):([0-5]\d)/
const str = `The current time is 16:24`
const result = regex.exec(str)
console.log(`The current hour is ${result[1]}`)
console.log(`The current minute is ${result[2]}`)

    第0个捕获组始终是整个匹配表达式。

上述脚本将产生如下输出。

The current hour is 16
The current minute is 24

做为额外的训练,你能够尝试修改此脚本,将24小时制转换为12小时制(am/pm)。
3 – 匹配日期
如今咱们来匹配一个DAY/MONTH/YEAR样式的日期模式。

\b(0?[1-9]|[12]\d|3[01])([\/\-])(0?[1-9]|1[012])\2(\d{4})

这个有点长,但它看起来与咱们上面讲过的有些相似。

    (0?[1-9]|[12]\d|3[01])——匹配1到31之间的任何数字(前面的0是可选的)
    ([\/\-])——匹配分隔符/或-
    (0?[1-9]|1[012])—— 匹配1到12之间的数字
    \2——匹配第二个捕获组(分隔符)
    \d{4}——匹配任意4位数(0000 – 9999)

这里惟一新的概念是,咱们使用\2来匹配第二个捕获组,即分隔符(/或-)。这使得咱们可以避免重复模式匹配规范,而且要求分隔符是一致的(若是第一个分隔符是/,那么第二个分隔符也必须同样)。
3.0 – 捕获组替换

经过使用捕获组,咱们能够动态地重组和转换咱们的字符串输入。

引用捕获组的标准方法是使用$或\符号,以及捕获组的索引(请记住捕获组元素是完整的捕获文本)。
3.1 – 真实示例 – 日期格式转换

假设咱们的任务是将使用国际日期格式(DAY/MONTH/YEAR)的文档集合转换为美式(MONTH/DAY/YEAR)日期样式。

咱们能够经过替换模式$3$2$1$2$4或\3\2\1\2\4使用上述正则表达式。

让咱们分解捕捉组。

    $1——第一个捕获组:日期。
    $2——第二个捕捉组:分隔符。
    $3——第三个捕获组:月份。
    $4——第四个捕获组:年份。

替换模式(\3\2\1\2\4)简单地交换了表达式中月份和日期的内容。

如下是咱们如何在Javascript中进行这种转换:

const regex = /\b(0?[1-9]|[12]\d|3[01])([ \/\-])(0?[1-9]|1[012])\2(\d{4})/
const str = `Today's date is 18/09/2017`
const subst = `$3$2$1$2$4`
const result = str.replace(regex, subst)
console.log(result)

上述脚本将打印Today's date is 09/18/2017到控制台。

一样的脚本在Python中是这样的:

import re
regex = r'\b(0?[1-9]|[12]\d|3[01])([ \/\-])(0?[1-9]|1[012])\2(\d{4})'
test_str = "Today's date is 18/09/2017"
subst = r'\3\2\1\2\4'
result = re.sub(regex, subst, test_str)
print(result)

4 – 电子邮件验证
正则表达式也可用于输入验证。

^[^@\s]+@[^@\s]+\.\w{2,6}$

以上是一个(过于简单的)Regex,用来匹配电子邮件地址。

    ^——输入开始
    [^@\s]——匹配除@和空格\s以外的任何字符
    +——1+次数
    @——匹配’@'符号
    [^@\s]+——匹配除@和空格以外的任何字符,1+次数
    \.——匹配’.'字符。
    \w{2,6}——匹配任何字符(字母,数字或下划线),2-6次
    $——输入结束

4.0 – 真实示例 – 验证电子邮件

假设咱们要建立一个简单的Javascript函数以检查输入是否为有效的电子邮件。

function isValidEmail (input) {
  const regex = /^[^@\s]+@[^@\s]+\.\w{2,6}$/g;
  const result = regex.exec(input)

  // If result is null, no match was found
  return !!result
}

const tests = [
  `test.test@gmail.com`, // Valid
  '', // Invalid
  `test.test`, // Invalid
  '@invalid@test.com', // Invalid
  'invalid@@test.com', // Invalid
  `gmail.com`, // Invalid
  `this is a test@test.com`, // Invalid
  `test.test@gmail.comtest.test@gmail.com` // Invalid
]

console.log(tests.map(isValidEmail))

此脚本的输出应为[ true, false, false, false, false, false, false, false ]。

    注意——在现实应用程序中,使用Regex验证电子邮件地址对于许多状况,例如用户注册,是不够的。可是一旦你确认输入的文本是电子邮件地址,那么你应该始终遵循发送确认/激活电子邮件的标准作法。

4.1 – 完整的电子邮件Regex
这是一个很是简单的例子,它忽略了许多很是重要的电子邮件有效性边缘状况,例如无效的开始/结束字符以及连续的周期。我真的不建议在你的应用程序中使用上述表达式;最好是使用一个有信誉的电子邮件验证库或继续探索更完整的电子邮件验证Regex。

例如,下面是一个来自emailregex.com的更高级的表达式,它匹配99%的RFC 5322兼容的电子邮件地址。

(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])

不过今天咱们不打算深刻探讨。
5 – 代码注释模式匹配
Regex最有用的特殊用法之一是能够成为代码重构器。大多数代码编辑器支持基于Regex的查找/替换操做。一个格式正确的Regex替换能够将繁琐的须要半小时忙碌的工做变成一个漂亮的Regex重构魔法。

不要编写脚原本执行这些操做,试着在你选择的文本编辑器中去作。几乎每一个文本编辑器都支持基于Regex的查找和替换。

如下是一些受欢迎的编辑器指南。

Sublime中的Regex替换——http://docs.sublimetext.info/en/latest/search_and_replace/search_and_replace_overview.html#using-regular-expressions-in-sublime-text

Vim中的Regex替换——http://vimregex.com/#backreferences

VSCode中的Regex替换——https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options

Emacs中的Regex替换——https://www.gnu.org/software/emacs/manual/html_node/emacs/Regexp-Replace.html
5.0 – 提取单行CSS注释

若是咱们想要查找CSS文件中的全部单行注释怎么办?

CSS注释以/* Comment Here */的格式出现。

要捕获任何单行CSS注释,咱们可使用如下表达式。

(\/\*+)(.*)(\*+\/)

    \/——匹配/符号(咱们有转义/字符)
    \*+——匹配一个或多个*符号(再次,咱们使用\来转义*字符)。
    (.*)——匹配任何字符(除了换行符\n),任意次数
    \*+——匹配一个或多个*字符
    \/——匹配关闭/符号。

注意,咱们已经在上面的表达式中定义了三个捕获组:开放字符((\/\*+)),注释内容((.*))和结束字符((\*+\/))。
5.1 – 真实示例 – 将单行注释转换为多行注释

咱们可使用此表达式经过执行如下替换将单行注释转换为多行注释。

$1\n$2\n$3

在这里,咱们只是在每一个捕获组之间添加了一个换行符\n。

尝试在有如下内容的文件上执行此替换。

/* Single Line Comment */
body {
  background-color: pink;
}

/*
 Multiline Comment
*/
h1 {
  font-size: 2rem;
}

/* Another Single Line Comment */
h2 {
  font-size: 1rem;
}

替换将产生相同的文件,但每一个单行注释转换为多行注释。

/*
 Single Line Comment
*/
body {
  background-color: pink;
}

/*
 Multiline Comment
*/
h1 {
  font-size: 2rem;
}

/*
 Another Single Line Comment
*/
h2 {
  font-size: 1rem;
}

5.2 – 真实示例 – 标准化CSS注释开头

假设咱们有一个又大又杂乱的CSS文件,是由几个不一样的人写的。在这个文件中,有些注释以/*开头,有些以/**开头,还有些以/*****开头。

让咱们来写一个Regex替换以标准化全部的单行CSS注释,以/*开头。

为了作到这一点,咱们将扩展表达式,只匹配以两个或更多星号开头的注释。

(\/\*{2,})(.*)(\*+\/)

这个表达式与原来的很是类似。主要区别在于开头咱们用\*{2,}替换了\*+。\*{2,}语法表示*的“两个或多个”实例。

为了规范每一个注释的开头,咱们能够经过如下替代。

/*$2$3

让咱们在如下测试CSS文件上运行此替换。

/** Double Asterisk Comment */
body {
  background-color: pink;
}

/* Single Asterisk Comment */
h1 {
  font-size: 2rem;
}

/***** Many Asterisk Comment */
h2 {
  font-size: 1rem;
}

结果将是与标准注释开头相同的文件。

/* Double Asterisk Comment */
body {
  background-color: pink;
}

/* Single Asterisk Comment */
h1 {
  font-size: 2rem;
}

/* Many Asterisk Comment */
h2 {
  font-size: 1rem;
}

6 – 匹配网址
另外一个很是有用的Regex是在文本中匹配URL。

下面是一个来自Stack Overflow的URL匹配表达式的示例。

(https?:\/\/)(www\.)?(?<domain>[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6})(?<path>\/[-a-zA-Z0-9@:%_\/+.~#?&=]*)?

    (https?:\/\/)——匹配http(s)
    (www\.)?——可选的“www”前缀
    (?<domain>[-a-zA-Z0-9@:%._\+~#=]{2,256}——匹配有效的域名
    \.[a-z]{2,6})——匹配域扩展扩展名(即“.com”或“.org”)
    (?<path>\/[-a-zA-Z0-9@:%_\/+.~#?&=]*)?——匹配URL路径(/posts)、查询字符串(?limit=1)和/或文件扩展名(.html),这些都是可选的。

6.0 – 命名捕获组
你注意到没有,一些捕获组如今以?<name>标识符开头。这是命名捕获组的语法,可使得数据提取更加清晰。
6.1 – 真实示例 – 从Web页面上的URL解析域名
如下是咱们如何使用命名捕获组来提取使用Python语言的网页中每一个URL的域名。

import re
import urllib.request

html = str(urllib.request.urlopen("https://moz.com/top500").read())
regex = r"(https?:\/\/)(www\.)?(?P<domain>[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6})(?P<path>\/[-a-zA-Z0-9@:%_\/+.~#?&=]*)?"
matches = re.finditer(regex, html)

for match in matches:
  print(match.group('domain'))

脚本将打印在原始网页HTML内容中找到的每一个域名。

...
facebook.com
twitter.com
google.com
youtube.com
linkedin.com
wordpress.org
instagram.com
pinterest.com
wikipedia.org
wordpress.com
...

7 – 命令行的用法

许多Unix命令行实用程序也支持Regex!咱们将介绍如何使用grep查找特定文件,以及使用sed替换文本文件内容。
7.0 – 真实示例 – 用grep匹配图像文件
咱们将定义另外一个基本的Regex,此次是用于匹配图像文件。
^.+\.(?i)(png|jpg|jpeg|gif|webp)$

    ^——开始行。
    .+——匹配任何字符(字母,数字,符号),除了\n(换行)以外,1+次数。
    \.——匹配 ‘.’字符。
    (?i)——表示下一个序列不区分大小写。
    (png|jpg|jpeg|gif|webp)——匹配常见的图像文件扩展名
    $——结束行

如下是如何列出Downloads目录中全部图像文件的方法。

ls ~/Downloads | grep -E '^.+\.(?i)(png|jpg|jpeg|gif|webp)$'

    ls ~/Downloads——列出Downloads目录中的文件
    |——将输出管道输送到下一个命令
    grep -E——使用正则表达式过滤输入

7.1 – 真实例子 – 用sed进行电子邮件替换

bash命令中正则表达式的另外一个好处是在文本文件中修改电子邮件。

这能够经过使用sed命令以及前面的电子邮件Regex的修改版本完成。

sed -E -i 's/^(.*?\s|)[^@]+@[^\s]+/\1\{redacted\}/g' test.txt

    sed——Unix的“流编辑器”实用程序,容许强大的文本文件转换。
    -E——使用扩展的Regex模式匹配
    -i——原位替换文件流
    's/^(.*?\s|)——将行的开头包装在捕获组中
    [^@]+@[^\s]+——电子邮件Regex的简化版本。
    /\1\{redacted\}/g'——用{redacted}替换每一个电子邮件地址。
    test.txt——对test.txt文件执行操做。

咱们能够在一个示例test.txt文件上运行上面的替换命令。

My email is patrick.triest@gmail.com

命令运行后,电子邮件将从test.txt文件中进行编辑。

My email is {redacted}

警告——此命令将自动从你传递的任何test.txt中删除全部电子邮件地址,所以,在运行它的时候要当心,由于此操做没法逆转。要在终端中预览结果,而不是替换原来的文本,只需省略-i标志。

注意——尽管上述命令适用于大多数Linux发行版,可是macOS使用BSD实现是sed,它在其支持的Regex语法中受到更多的限制。要在MacOS上使用sed,并具备体面的正则表达式支持,我建议使用brew install gnu-sed安装sed的GNU实现,而后从命令行使用gsed而不是sed。
8 – 何时不使用Regex

好的,知道Regex是一个强大又灵活的工具了吧?!那么,有没有应该避免编写Regex的时候?有!
8.0 – 语言解析

解析结构化语言,从英语到Java到JSON,使用正则表达式都是一种真正的痛苦。

当数据源中的边缘状况或次要语法错误致使表达式失败时,将致使最终(或即时)的灾难,出于此目的去编写你本身的正则表达式可能会让你心情沮丧。

强化的解析器几乎可用于全部机器可读的语言,而NLP工具可用于人类语言——我强烈建议你使用其中一种,而不是尝试编写本身的语言。
8.1 – 安全 – 输入过滤和黑名单

使用Regex过滤用户输入(例如来自Web表单),以及防止黑客向应用程序发送恶意命令(例如SQL注入),看上去彷佛很诱人。

在这里使用自定义的Regex是不明智的,由于它很难覆盖每一个潜在的攻击向量或恶意命令。例如,黑客可使用替代字符编码绕过编写得不全面的输入黑名单过滤器。

这是另外一个实例,对此我强烈建议你使用通过良好测试的库和/或服务,以及使用白名单而不是黑名单,以保护你的应用程序免受恶意输入。
8.2 – 性能密集的应用程序

正则表达式的匹配速度能够从不是很是快到极慢的范围变更,取决于表达式写得怎么样。对于大多数用例来讲,这很好,特别是若是匹配的文本很短(例如电子邮件地址表单)的话。然而,对于高性能服务器应用程序,正则表达式会成为性能瓶颈,特别是若是表达式写得很差或被搜索的文本很长的话。
8.3 – 对于不须要Regex的地方

正则表达式是一个很是有用的工具,但这并不意味着你应该在任何地方使用它。

若是问题有替代的解决方案,解决方案更简单和/或不须要使用Regex,那么请不要只是为了显摆而使用Regex。Regex很棒,但它也是最不可读的编程工具之一,并且很容易出现边缘状况和bug。

过分使用Regex会让你的同事(以及须要工做在你的代码上的任何人)生气恼怒,甚至巴不得揍你一顿。
结论

我但愿这是对Regex的许多用途的一个有用的介绍。

还有不少Regex的用例是咱们没有涵盖的。例如,能够在PostgreSQL查询中使用Regex来动态地搜索数据库中的文本模式。

咱们还漏下了许多强大的Regex语法特性没有覆盖,如lookahead,lookbehind,atomic groups,recursion和subroutines。

要提升正则表达式技能并了解有关这些功能的更多信息,我推荐如下资源。

    Learn Regex The Easy Way - https://github.com/zeeshanu/learn-regex
    Regex101 - https://regex101.com/
    HackerRank Regex Course - https://www.hackerrank.com/domains/regex/re-introduction

本教程中示例的源代码能够在Github存储库中找到—— https://github.com/triestpa/You-Should-Learn-Regex

欢迎随时对本教程提出任何建议、见解或批评。java

相关文章
相关标签/搜索