PowerShellでいろいろな文字コードを扱う
みんな大好きPowerShellのGet-Contentさんですが、日本語使うには文字コードの扱いがイマイチです。
Encodingなしでざっと試験してみると、
ま、変なコード体系が多い日本語がいけないんですけどね。
Encodingありは、Out-Fileさんと同じ。
(ってか、ヘルプにEncodingの記述がないのは何の嫌がらせですかね?Get-Commandで調べないと)
Out-Fileさんは、
- -Encodingを指定しないと、utf16-le-bomあり
- -Encodingは「unknown、string、unicode、bigendianunicode、utf8、utf7、utf32、ascii、default、oem」で
defaultやoemは日本語環境でやっているからでしょう。
とてもいただけないのが、BOMなしUTF-8(utf8n)を読めないこと。
ま、Encodingを指定すれば良いんですが、そんなもんはお前が判定しろよ、と。
ということで、簡単な文字コード判定方法を調べてみた。
C# から IMultiLanguage2::DetectInputCodepage() を使う方法 - てっく煮ブログ
これを、PowerShellの関数に乗せてみたんだけど、、、
読みたいutf8nのファイルが、iso-8859-1だといわれる、、、。
そんなわけで小細工していろいろそぎ落として完成したのがこれ。
Get-Encoding.ps1
Param( $Path, [switch]$List, [int]$Max = 10 ) $namespaceName = 'Mlang' $className = 'Encoding' $classFullName = '{0}.{1}' -f $namespaceName, $className if(-not ($classFullName -as [type])) { Add-Type @" using System; using System.Runtime.InteropServices; namespace $namespaceName { public class $className { public static DetectEncodingInfo[] Detect(byte[] bytes, int max) { DetectEncodingInfo[] ret = null; IMultiLanguage2 lang = (IMultiLanguage2)new MultiLanguage(); int len = bytes.Length, scores = max, i; DetectEncodingInfo[] infos = new DetectEncodingInfo[scores]; for (i = 0; i < scores; i++) infos[i] = new DetectEncodingInfo(); // bytes to IntPtr GCHandle hbytes = GCHandle.Alloc(bytes, GCHandleType.Pinned); IntPtr pbytes = Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0); GCHandle hinfos = GCHandle.Alloc(infos, GCHandleType.Pinned); IntPtr pinfos = Marshal.UnsafeAddrOfPinnedArrayElement(infos, 0); try { if (lang.DetectInputCodepage(0, 0, pbytes, ref len, pinfos, ref scores) == 0 && scores >= 0) { ret = new DetectEncodingInfo[scores]; for (i = 0; i < scores; i++) ret[i] = infos[i]; } } finally { if (hinfos.IsAllocated) hinfos.Free(); if (hbytes.IsAllocated) hbytes.Free(); } return ret; } } public struct DetectEncodingInfo { public UInt32 nLangID; public UInt32 nCodePage; public Int32 nDocPercent; public Int32 nConfidence; }; [ComImport, Guid("275c23e2-3747-11d0-9fea-00aa003f8646")] public class MultiLanguage { } [Guid("DCCFC164-2B38-11D2-B7EC-00C04F8F5D9A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IMultiLanguage2 { void GetNumberOfCodePageInfo(); void GetCodePageInfo(); void GetFamilyCodePage(); void EnumCodePages(); void GetCharsetInfo(); void IsConvertible(); void ConvertString(); void ConvertStringToUnicode(); void ConvertStringFromUnicode(); void ConvertStringReset(); void GetRfc1766FromLcid(); void GetLcidFromRfc1766(); void EnumRfc1766(); void GetRfc1766Info(); void CreateConvertCharset(); void ConvertStringInIStream(); void ConvertStringToUnicodeEx(); void ConvertStringFromUnicodeEx(); void DetectCodepageInIStream(); int DetectInputCodepage( [In] UInt32 dwFlag, [In] UInt32 dwPrefWinCodePage, [In] IntPtr pSrcStr, [In, Out] ref Int32 pcSrcSize, [In] IntPtr lpEncoding, [In, Out] ref Int32 pnScores); void ValidateCodePage(); void GetCodePageDescription(); void IsCodePageInstallable(); void SetMimeDBSource(); void GetNumberOfScripts(); void EnumScripts(); void ValidateCodePageEx(); } } "@ } $encoding = $classFullName -as [type] if($Path -is [string]) { $infos = $encoding::Detect([System.IO.File]::ReadAllBytes($Path), $Max) } else { $infos = $encoding::Detect($Path, $Max) } $ret = $null if($List) { $ret = $infos } else { for($i = 0; $i -lt $infos.Count; $i++) { $ret = [System.Text.Encoding]::GetEncoding([int]$infos[$i].nCodePage) if($infos[$i].nLangID -eq 0xffffffff) { break } } } return $ret
期待したものが返ってこなかったら、-Listつけて応答見て、最後のfor文の中変えてみて。
、、、もっとも、そもそも選択肢に上がらない場合もあるけどね。
なんか、nDocPercentとかnConfidenceがやたら-1を返してくる、、、。
あ、名前空間やクラス名が気に入らない場合は、$namespaceNameとか$classNameとか変更して。
そこを変えればOKなように作ったのし。
んでまあ、これに判定させてから-Encodingを指定すればOKというすんぽーです。