PowerShell 5.1正则表达式初探

环境

操作系统Win10 1903,PowerShell版本($PSVersionTable.PSVersion.ToString())5.1.18362.145。另外,$PSCultureja-JP$PSUICultureen-US,天知道我做了什么。虽然已经出了Windows Terminal (Preview),但不够稳定,暂且先使用系统自带的终端。

缘由

本身我就对PowerShell很感兴趣,正则表达式又经常能用到,PowerShell因为是.NET系的所以正则支持还不错。学了又不会亏,我这么想。

参考

使用

-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的话,可以看到Name0的那边,Valueing

-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。

InSb

InSb

只是跟工作和生活相关的记录