正則表達式,軟件工程中最爲強大,且普遍適用,使人信服的技術之一。從驗證電子郵件地址到執行復雜的代碼重構器,正則表達式的用途很是普遍,是任何軟件工程師工具箱中必不可少的條目。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