环境
操作系统Win10 1903,PowerShell版本($PSVersionTable.PSVersion.ToString()
)5.1.18362.145。另外,$PSCulture
为ja-JP
,$PSUICulture
为en-US
,天知道我做了什么。虽然已经出了Windows Terminal (Preview),但不够稳定,暂且先使用系统自带的终端。
缘由
本身我就对PowerShell很感兴趣,正则表达式又经常能用到,PowerShell因为是.NET系的所以正则支持还不错。学了又不会亏,我这么想。
参考
- 正则表达式30分钟入门教程
- About Regular Expressions | Microsoft Docs
- Regular Expression Language - Quick Reference | Microsoft Docs
使用
-match
这个操作符很常用。其语法是<string[]> -match <regular-expression>
,前者<string[]>
是要被匹配的,后者<regular-expression>
可以是"(?<=\brac)\w+\b"
这样的正则pattern字符串,也可以是诸如[regex]::new("(?<=\brac)\w+\b")
构造出来的System.Text.RegularExpressions.Regex
类型的实例。
另外还有Select-String这个cmdlet,常用于从多行字符串对象……什么的来匹配。
-match例
比如说,要从"racing"
中取出"ing"
。可以直接
"racing" -match "(?<=\brac)\w+\b"
也可以
$a = [regex]::new("(?<=\brac)\w+\b")
"racing" -match $a
确实可以匹配到,故返回True
。
至于匹配的结果,可以从$Matches
这个automatic variable中拿到。它是个Hashtable。由于我们只需要从pattern中整体匹配到的结果,也就是组号0,所以
$Matches.0
不止这一种写法,个人更习惯写成$Matches[0]
。直接得出ing
。
如果直接用$Matches
的话,可以看到Name
为0
的那边,Value
为ing
。
-replace例
还算常用?-replace
后接正则表达式,痛快。详情见about_comparison_operators那边。
比如说想把The_Game_Paradise!_-_Master_of_Shooting!_(Jaleco_Mega_System_32)
里的下划线全部替换成空格:
"The_Game_Paradise!_-_Master_of_Shooting!_(Jaleco_Mega_System_32)" -replace "_", " "
就可以了。
需要注意的是,JavaScript那边需要用g
这样的flag才能够global search。如:
"The_Game_Paradise!_-_Master_of_Shooting!_(Jaleco_Mega_System_32)".replace(/_/g, " ");
PowerShell就不必了。
更新(2020/09/08)
从Firefox 77和Chrome 85开始,我们可以用String.prototype.replaceAll()
这个方法做同样的事:
'The_Game_Paradise!_-_Master_of_Shooting!_(Jaleco_Mega_System_32)'.replaceAll('_', ' ');
但请注意,虽然第一个参数允许string或RegExp
对象,但RegExp
对象需要有global flag才行。详见MDN。
这个是2021 feature,覆盖情况见Can I use...。现在这个时点的Edge恰好是85,建议试试。
说来有趣,之前做项目的时候恰好用的是Firefox 77 DevEd,也稀里糊涂地用了这个方法,后来在其他浏览器上测试时才发现。恐怕我用得太早了www
Select-String例
最好弄个文件来试。现在还没工夫。开整。
有一次看到Capcom的官方译名,感觉好奇怪,就想着,干脆点,来点古风可好w
D:
# 把rime-middle-chinese的dict.yaml强行拖下来
Invoke-WebRequest
-Uri 'https://github.com/rime/rime-middle-chinese/raw/master/zyenpheng.dict.yaml'
-OutFile 'zyenpheng.dict.yaml'
# 看看是不是
Get-Content .\zyenpheng.dict.yaml -TotalCount 5
# 找行内容看看
(Get-Content .\zyenpheng.dict.yaml -TotalCount 32)[-1]
<#
哎 qad
#>
结构很明显了:汉字,空白符(其实是tab。从终端输出再复制会不一样),一串拉丁字母。先用\t
匹配tab,分隔一下。就这一串拉丁字母来说,找kh开头p结尾,而且之后就是行结尾的行:
Select-String .\zyenpheng.dict.yaml -Pattern '\tkh.+p$'
另外一提,Select-String以行为单位,就是说,行里含有能匹配的字符串就可以。
之后是,找kh开头,之后有m但它不一定是结尾的行。为防止词语的影响,其后不能有空格才行:
Select-String .\zyenpheng.dict.yaml -Pattern '\tkh.+m\w*'
很多东西还不会,就不限制太多了,随便玩玩。
高阶功能
[regex]::Replace
刚才说的那个-replace
只能将匹配的地方替换成给定的字符串,并不能把组之类的拿过来用。
不过呢,我们可是守着.NET的大宝库,用就完事儿了。
来一个实际遇到的例子,可能有点复杂:
# 一个字符串,有些部分是\u开头的四位hex的UTF-16编码:
$ustr = 'NAME = \u6587\u5B57\u5316\u3051'
# 我们需要把这些编码部分还原成字符才行:
[regex]::Replace($ustr, '\\u([0-9a-fA-F]{4})', {
[char]::ConvertFromUtf32(
[System.Convert]::ToUInt16($args.groups[1], 16)
)
}
)
# 显示结果:NAME = 文字化け
确实有点复杂……实际用到的东西经常要组合一堆东西。这个其实是解java的.properties
文件用的。
可以看一下Regex.Replace Method的文档,用的是Replace(String, String, MatchEvaluator)
这个。MatchEvaluator Delegate的文档也还能看吧。
功能覆盖
Zero-length Lookbehind Assertion
管它怎么翻译,总之就是(?<=)
和(?<!)
。
刚才也看到了,可以用。不过在JavaScript方面,比如说"racing".match(/(?<=\brac)\w+\b/)
,在Edge Insider dev(使用Chromium的内核)77.0.197.1是可以的,但Firefox DevEd 68.0b14不可。非得说的话,可以规避这个特性来实现效果,但这可真难读。
这类特性,说是ES2016+的就好。比照一下compat-table可知,那些使用相对较新的V8引擎的,各种东西,可以用,其中包括Node。另外还有Can I use...,这个更方便。
更新(2020/06/02)
在Firefox DevEd 78.0b1测试时可以用。他们在版本78对正则表达式来了一手史诗级加强🎉
Balancing Group
平衡组。.NET特有?可能吧。
应该可以用,不想试太多了。上面那个“30分钟入门”里有写,可以玩玩。
UTF-8
这是个痛。Windows这边用UTF-16差点刹不住车,而PowerShell 5.1也就\u四位十六进制
的能用,还得牵扯到代理对(surrogate pair)的问题。
好在PowerShell Core 6提供了新的special character,就是`u{最多六位十六进制}
,希望能用。
更新(2020/06/14)
我当时在说什么www
我的意思是JavaScript的RegExp.prototype.unicode:
'𠀀'.match(/𠀀/)
// -> Array [ "𠀀" ]
'𠀀'.match(/\u{20000}/)
// -> null
'𠀀'.match(/\u{20000}/u)
// -> Array [ "𠀀" ]
用JavaScript的话,如果打开u
这个flag,\u{hhhhh}
这种形式就有效了。
pwsh 6开始有的`u{hhhhhh}
虽然形式上挺像的,但它并不是正则表达式的东西,只是新增的转义字符而已。
dotNET的正则至今还没有实现这种功能,建议适当使用代理对来规避。
后记
这边术语很混乱,直接用英文反倒好些。要是读起来难受,抱歉了。
以后还要试试Select-String
和PowerShell Core 6。