PowerShell代码备忘录

不定时更新。主要是些常用的one-liner。

缘起

常用的PowerShell代码段写在本子上自己也不看,干脆写博客里还方便点儿。

环境

未特地指明的话:

操作系统Win10 1909,PowerShell版本($PSVersionTable.PSVersion.Major)是5,$PSCulture$PSUICulture均为ja-JP

若指明PowerShell版本,只代表在默认环境下不可用而改用指定版本,不代表指定版本以下就不好使。

正文

URL Encoding解码

Add-Type -AssemblyName System.Web
$s = 'https://ja.wikipedia.org/wiki/%E5%8D%8A%E8%A7%92%E3%82%AB%E3%83%8A'
[System.Web.HttpUtility]::UrlDecode($s)
# → https://ja.wikipedia.org/wiki/半角カナ

值得一提的是,[System.Uri]::UnescapeDataString()虽然也能做到类似的事,但它只是将percent-encoding处理好而已。现在常用的是application/x-www-form-urlencoded,半角空格会被处理成+号。就这个来说,UrlDecode能处理好而UnescapeDataString不行。

字符实体引用解码

字符实体引用,我看了一下日文维基百科,上面是“文字参照(character reference)”。行吧,反正已经乱了,不管那么多。

Add-Type -AssemblyName System.Web
$s = '<div id="siteSub" class="noprint">出典: フリー百科事典『ウィキペディア(Wikipedia)』</div>'
[System.Web.HttpUtility]::HtmlDecode($s)
# → <div id="siteSub" class="noprint">出典: フリー百科事典『ウィキペディア(Wikipedia)』</div>

顺带一提,这种东西实际上分为两种,一种是所谓“数値文字参照”,如&#30334;);一种是所谓“文字実体参照”,如我们耳熟能详的&gt;>)。

Unicode码位获取

码位……说code point应该大家都知道吧。

[char]::ConvertToUtf32('百',0)
# → 30334,十进制表示
'0x{0:X}' -f [char]::ConvertToUtf32('百', 0)
# → 0x767E,十六进制表示

哪怕它超出了Unicode的BMP(基本多言語面,U+0000~U+FFFF)的范围也可以搞定。

'0x{0:X}' -f [char]::ConvertToUtf32('🥳', 0)
# → 0x1F973

PowerShell 7那边,由于采用的是dotNET Core 3,System.Text.Rune就可以用了:$PSVersionTable.PSVersion.ToString(): 7.0.0-preview.6

'0x{0:X}' -f [System.Text.Rune]::GetRuneAt('🥳' , 0).Value
# → 0x1F973
[System.Text.Rune]::new(0x1F973).ToString() -eq '🥳'
# → True

Base64编码与解码

$enc = [System.Text.Encoding]::UTF8

$s = 'お前だけ消費税100な'
[System.Convert]::ToBase64String($enc.GetBytes($s))
# → 44GK5YmN44Gg44GR5raI6LK756iOMTAw44Gq

$s = '44GK5YmN44Gg44GR5raI6LK756iOMTAw44Gq'
$enc.GetString([System.Convert]::FromBase64String($s))
# → お前だけ消費税100な

UTF8 ⇔ UTF7

$s = 'お前だけ消費税100な'
[System.Text.Encoding]::UTF8.GetString(
  [System.Text.Encoding]::UTF7.GetBytes($s))
# → +MEpSTTBgMFFtiIy7eg4-100+MGo-

$s = '+MEpSTTBgMFFtiIy7eg4-100+MGo-'
[System.Text.Encoding]::UTF7.GetString(
  [System.Text.Encoding]::UTF8.GetBytes($s))
# → お前だけ消費税100な

另一种办法:

$utf8 = [System.Text.Encoding]::UTF8
$utf7 = [System.Text.Encoding]::UTF7

$s = 'お前だけ消費税100な'
$utf8.GetString(
  [System.Text.Encoding]::Convert($utf8, $utf7, $utf8.GetBytes($s)))
# → +MEpSTTBgMFFtiIy7eg4-100+MGo-
$s = '+MEpSTTBgMFFtiIy7eg4-100+MGo-'
$utf7.GetString(
  [System.Text.Encoding]::Convert($utf7, $utf8, $utf7.GetBytes($s)))
# → お前だけ消費税100な

稍微清晰了一些,但更繁琐。

全角片假名转换为半角

文档:Strings.StrConv(String, VbStrConv, Int32) Method

Add-Type -AssemblyName Microsoft.VisualBasic
[regex]::Replace('ウィキペディア', '\p{IsKatakana}', {
  [Microsoft.VisualBasic.Strings]::StrConv($args.Groups[0].Value, [Microsoft.VisualBasic.VbStrConv]::Narrow, 0)})
# → ウィキペディア

竟然要的是Microsoft.VisualBasic,非常有意思。

其实那个正则表达式替换成\p{IsKatakana}+更好,它支持一并处理。

半角片假名转换为全角

 [regex]::Replace("ウィキペディア", "[\uFF61-\uFF9F]+", { $args.Groups[0].Value.Normalize(5) })
# → ウィキペディア

这个,就不只是有意思那么简单了。

  • "[\uFF61-\uFF9F]+":半角片假名的范围是U+FF61\~U+FF9F。这个范围属于Halfwidth and Fullwidth Forms,U+FF00\~U+FFEF。由于并没有自己独有的区块,只好这么做。
  • String.Normalize:用日语来说就是“Unicode正規化”。参见String.Normalize Method
  • .Normalize(5):里面那个5其实表示FormKC,参见NormalizationForm Enum

稍微直观一点的内容参见:Unicode正規化とは。例子挺多,好看。

至于说,为什么不能像上面那样对单个假名用[Microsoft.VisualBasic.VbStrConv]::Wide这样的办法处理,因为只对单个字符操作的话,这个Wide只会将浊点和半浊点转化为它的单独形式(U+309B和U+309C),无可厚非,但并不是我们想要的。

不过我们可以一并处理:

Add-Type -AssemblyName Microsoft.VisualBasic
[regex]::Replace("ウィキペディア", "[\uFF61-\uFF9F]+", {
  [Microsoft.VisualBasic.Strings]::StrConv($args.Groups[0].Value, [Microsoft.VisualBasic.VbStrConv]::Wide, 0) })
# → ウィキペディア

这样也是可以的。

全角 ⇔ 半角(P/Invoke kernel32.dll)

开启修罗模式!

由于代码量相对庞大,已经拆到这边了:P/Invoke kernel32.dll实现全角半角转换

还是这句话:没什么必要用www

用ANSI escape code打印8bit色盘

$sb = [System.Text.StringBuilder]::new()
foreach ($_ in 0..255) {
  $sb.Append("$([char]0x1B)[48;5;${_}m ") > $null }
Write-Host $sb

老漂亮了。

PowerShell 6及之后可以把$([char]0x1B)替换为`e,稍微简化了一些。

简单的首字母大写

(Get-Culture).TextInfo.ToTitleCase("burning force")
# → Burning Force

(Get-Culture)的返回值类型是[cultureinfo]。暂且按照PowerShell这边的方式写了。

但是如果说mcDonald这种情况,我们不想让中间的字母大小写发生变化:

[regex]::Replace("mcDonald", "\b(.)", { $args.Groups[1].Value.ToUpper() })
# → McDonald

使用了祖传的[regex]::Replace。这样才符合预期。

数组内元素转换并返回新数组

举个例子,比如说我们有这样一个数组:

$arr = [string[]]@('1', '2', '3')

元素全都是能转换成int的string。就是说我们可以用[int]::Parse搞定。但是要是一个一个来,未免太麻烦。

我们可以使用[array]::ConvertAll官方文档):

$result = [array]::ConvertAll(
  $arr,
  [System.Converter[string, int]]{
    Param($i)
    [int]::Parse($i) })

这样$result.GetType().Name就是Int32[],结果也没有问题。

这里涉及到了Converter这个delegate(官方文档)。至于说在PowerShell如何使用,请见这里

这里的$i类型就是System.String

之所以有这样的需求,是因为做了这样的题:Basic Math (Add or Subtract)

关于反射

虽然非常没必要,要是特别闲的话可以用反射的办法(System.Reflection.MethodBase.Invoke)。需要注意的是,System.Array.ConvertAll用到了范型:

$result = [array].GetMethod("ConvertAll").
  MakeGenericMethod(@([string], [int])).
  Invoke(
    $null,
    @([string[]]@('1', '2', '3'), [System.Converter[string, int]]{ [int]::Parse($args) }))

由于本例中ConvertAll是个静态方法,这个Invoke的第一个参数并没作用。详情看文档。

稍稍注意一下,$args$args[0]的类型不同,分别是System.Object[]System.String。看来可以自动拆开,先不管也罢,估计是笔烂账。要是用上次提到的Param($i)$i类型还是System.String

没用到范型的,比如System.Math.Abs,用System.Type.InvokeMember

$r = [System.Math].InvokeMember(
  "Abs",
   [System.Reflection.BindingFlags]::InvokeMethod,
   $null,
   $null,
   -3)

就是求绝对值嘛。$r结果是3

简体繁体转换

虽然.NET自带这样的功能,不过还是建议用其他的库。总之先试试:

Add-Type -AssemblyName Microsoft.VisualBasic
using namespace Microsoft.VisualBasic
[Strings]::StrConv("神龜雖壽", [VbStrConv]::SimplifiedChinese, 2052)
# → 神龟虽寿
[Strings]::StrConv("神龟虽寿", [VbStrConv]::TraditionalChinese, 2052)
# → 神龜雖壽

由于是5.1的默认终端,输入时显示会有问题,不过不影响结果。

另外,using namespace Microsoft.VisualBasic就如同C#那边的using那样,现在写[Strings]::StrConv就相当于写[Microsoft.VisualBasicStrings]::StrConv,这不多提。

但是吧,如果使用简写,补全功能就不好用了,心里没底,建议在写脚本时再用using namespace吧。不过这个问题在7.0.0-rc.1时修正了,可以正常补全,打到[Strings]::StrC再Tab倒是没问题。不过比如说,打到[Strings再按Tab,还是会补全成[Microsoft.VisualBasic.Strings,看着很长啊。

StrConv的第三个参数是int LocaleID = 0,Locale ID参见文档:2.1.1906 Part 4 Section 7.6.2.39, LCID (Locale ID)

至于说为什么简转繁或者反过来都写2052,也就是zh-CN那边,咱也不太清楚原因,写1028也就是zh-TW那边结果又不太对劲。

字符串反转

我寻思.NET那边不怎么擅长这个www

基于.NET的char,使用string

Add-Type -AssemblyName System.Linq.Enumerable
@([System.Linq.Enumerable]::Reverse("神龜雖壽")) -join ""
# → 壽雖龜神

当然这样不太好,毕竟一个char并不一定是我们所说的“一个字”。

基于string(以grapheme为单位),使用TextElementEnumerator

Add-Type -AssemblyName System.Linq.Enumerable
Add-Type -AssemblyName System.Globalization
[System.Linq.Enumerable]::Reverse([string[]]@([System.Globalization.StringInfo]::GetTextElementEnumerator("🎢右臂有个鸟🎢"))) -join ""
# → 🎢鸟个有臂右🎢

显得冗长,不过起码能用。

再就是,打字带空格那边也值得看,不过也就开头那点儿有用。

想要在反转之后的基础上插空格什么的,很自然地只需要改一下-join的内容就好。

后记

真好用,用就完事儿了。以后有需要再补充。

InSb

InSb

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