最近、何かとWindows Powershellを触る機会が多い。
Powershellといえばオブジェクト指向のシェル。
巨大な.NETライブラリを利用できる上、そのオブジェクトをパイプラインで渡すことができるので非常に便利。
ただ、Bashとかの従来のテキストベースシェルに比べると扱い方が特殊。
もともと.NETerな方々ならともかく、それ以外の人にとっては結構学習コストが高いのではないかと思う。
そんなわけで、テキストベースシェルを使っている人向けにPowershellの使い方の簡単なメモを書いてみる。
標準出力とパイプで渡されるものは別物
テキストベースシェルを使っていた人がPowershellを使って一番戸惑うのが、
コンソールへの出力とパイプで渡されるデータが一致しないことだと思う。
こんな感じ。
PS C:\work> Get-Childitem
ディレクトリ: C:\work
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2018/04/04 20:31 testfolder
-a---- 2018/04/04 20:31 70 test.txt
-a---- 2018/04/04 20:32 70 test_.txt
-a---- 2018/04/04 20:33 86 test_1.txt
-a---- 2018/04/04 20:33 164 test_2.txt
# コンソールに出力される項目は、[Mode]、[LastWriteTime]、[Length]、[Name]
# テキストベースシェルだと、パイプで渡せる情報はこれだけのはず
# ところが、Powershellの場合…
PS C:\work> Get-Childitem | Select-Object Directory,Name,CreationTime,LastAccessTime
Directory Name CreationTime LastAccessTime
--------- ---- ------------ --------------
testfolder 2018/04/04 20:31:32 2018/04/04 20:31:32
C:\work test.txt 2018/04/04 20:30:48 2018/04/04 20:30:48
C:\work test_.txt 2018/04/04 20:32:48 2018/04/04 20:32:48
C:\work test_1.txt 2018/04/04 20:33:13 2018/04/04 20:33:13
C:\work test_2.txt 2018/04/04 20:32:54 2018/04/04 20:32:54
# コンソールに表示されてない[Directory]、[CreationTime]、[LastAccessTime]を出力することができる!
ディレクトリ: C:\work
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2018/04/04 20:31 testfolder
-a---- 2018/04/04 20:31 70 test.txt
-a---- 2018/04/04 20:32 70 test_.txt
-a---- 2018/04/04 20:33 86 test_1.txt
-a---- 2018/04/04 20:33 164 test_2.txt
# コンソールに出力される項目は、[Mode]、[LastWriteTime]、[Length]、[Name]
# テキストベースシェルだと、パイプで渡せる情報はこれだけのはず
# ところが、Powershellの場合…
PS C:\work> Get-Childitem | Select-Object Directory,Name,CreationTime,LastAccessTime
Directory Name CreationTime LastAccessTime
--------- ---- ------------ --------------
testfolder 2018/04/04 20:31:32 2018/04/04 20:31:32
C:\work test.txt 2018/04/04 20:30:48 2018/04/04 20:30:48
C:\work test_.txt 2018/04/04 20:32:48 2018/04/04 20:32:48
C:\work test_1.txt 2018/04/04 20:33:13 2018/04/04 20:33:13
C:\work test_2.txt 2018/04/04 20:32:54 2018/04/04 20:32:54
# コンソールに表示されてない[Directory]、[CreationTime]、[LastAccessTime]を出力することができる!
Powershellでは、標準出力(に相当する「Standard output stream」)とコンソール上の表示は別物なのだ。
これが一番分かりやすいのが、[Write-Output]と[Write-Host]の両コマンドレットの比較。
PS C:\work> Write-Output "hoge"
hoge
PS C:\work> Write-Host "hoge"
hoge
# [Write-Output] も [Write-Host]も、コンソール上で実行するだけなら結果は同じ
PS C:\work> $a = Write-Output "hoge"
PS C:\work> $a
hoge
PS C:\work> Write-Output "C:\work\hoge" | Split-Path -Leaf
hoge
# [Write-Output]は[Standard output stream]への出力なので、
# 結果を変数に格納したりパイプで渡したりできるが…
PS C:\work> $b = Write-Host "hoge"
hoge
PS C:\work> $b
PS C:\work> Write-Host "C:\work\hoge" | Split-Path -Leaf
C:\work\hoge
# [Write-Host]はあくまでホスト(コンソール)に表示するだけなので、
# 結果を変数に格納したりパイプで渡したりできない!
hoge
PS C:\work> Write-Host "hoge"
hoge
# [Write-Output] も [Write-Host]も、コンソール上で実行するだけなら結果は同じ
PS C:\work> $a = Write-Output "hoge"
PS C:\work> $a
hoge
PS C:\work> Write-Output "C:\work\hoge" | Split-Path -Leaf
hoge
# [Write-Output]は[Standard output stream]への出力なので、
# 結果を変数に格納したりパイプで渡したりできるが…
PS C:\work> $b = Write-Host "hoge"
hoge
PS C:\work> $b
PS C:\work> Write-Host "C:\work\hoge" | Split-Path -Leaf
C:\work\hoge
# [Write-Host]はあくまでホスト(コンソール)に表示するだけなので、
# 結果を変数に格納したりパイプで渡したりできない!
パイプで渡されているものの正体は?
テキストベースのシェルに慣れていると、「パイプで何が渡されているのか分からない」
というのは気持ちが悪く感じるかもしれない(私は凄く気持ち悪かった)。
「オブジェクトが渡される」とはいうけど、具体的にはどんなものが渡されているのか。
これの確認には、[Get-Member]と[Select-Object *]を使用すると良い。
※単純に型名が欲しいだけなら、GetType()も便利
※単純に型名が欲しいだけなら、GetType()も便利
PS C:\work> $list = Get-Childitem
PS C:\work> $list[1]
ディレクトリ: C:\work
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2018/04/04 20:31 70 test.txt
PS C:\work> $list[1] | Get-Member
TypeName: System.IO.FileInfo # 型名
Name MemberType Definition # プロパティとメソッド
---- ---------- ----------
LinkType CodeProperty System.String LinkType{get=GetLinkType;}
Mode CodeProperty System.String Mode{get=Mode;}
Target CodeProperty System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=...
AppendText Method System.IO.StreamWriter AppendText()
CopyTo Method System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(s...
Create Method System.IO.FileStream Create()
# …(中略)
Attributes Property System.IO.FileAttributes Attributes {get;
CreationTime Property datetime CreationTime {get;set;}
CreationTimeUtc Property datetime CreationTimeUtc {get;set;}
Directory Property System.IO.DirectoryInfo Directory {get;}
# …(長いので以下省略)
# [Get-Member]は、オブジェクトの型とメソッド・プロパティをまとめて確認できる!
PS C:\work> $list[1] | Select-Object *
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\work\test.txt
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\work
PSChildName : test.txt
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
Mode : -a----
VersionInfo : File: C:\work\test.txt
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language:
BaseName : test
Target : {}
LinkType :
Name : test.txt
Length : 70
DirectoryName : C:\work
Directory : C:\work
IsReadOnly : False
Exists : True
FullName : C:\work\test.txt
Extension : .txt
CreationTime : 2018/04/04 20:30:48
CreationTimeUtc : 2018/04/04 11:30:48
LastAccessTime : 2018/04/04 20:30:48
LastAccessTimeUtc : 2018/04/04 11:30:48
LastWriteTime : 2018/04/04 20:31:17
LastWriteTimeUtc : 2018/04/04 11:31:17
Attributes : Archive
# [Select-Object *]は、各プロパティの内容を確認できる!
PS C:\work> $list[1]
ディレクトリ: C:\work
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2018/04/04 20:31 70 test.txt
PS C:\work> $list[1] | Get-Member
TypeName: System.IO.FileInfo # 型名
Name MemberType Definition # プロパティとメソッド
---- ---------- ----------
LinkType CodeProperty System.String LinkType{get=GetLinkType;}
Mode CodeProperty System.String Mode{get=Mode;}
Target CodeProperty System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=...
AppendText Method System.IO.StreamWriter AppendText()
CopyTo Method System.IO.FileInfo CopyTo(string destFileName), System.IO.FileInfo CopyTo(s...
Create Method System.IO.FileStream Create()
# …(中略)
Attributes Property System.IO.FileAttributes Attributes {get;
CreationTime Property datetime CreationTime {get;set;}
CreationTimeUtc Property datetime CreationTimeUtc {get;set;}
Directory Property System.IO.DirectoryInfo Directory {get;}
# …(長いので以下省略)
# [Get-Member]は、オブジェクトの型とメソッド・プロパティをまとめて確認できる!
PS C:\work> $list[1] | Select-Object *
PSPath : Microsoft.PowerShell.Core\FileSystem::C:\work\test.txt
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\work
PSChildName : test.txt
PSDrive : C
PSProvider : Microsoft.PowerShell.Core\FileSystem
PSIsContainer : False
Mode : -a----
VersionInfo : File: C:\work\test.txt
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language:
BaseName : test
Target : {}
LinkType :
Name : test.txt
Length : 70
DirectoryName : C:\work
Directory : C:\work
IsReadOnly : False
Exists : True
FullName : C:\work\test.txt
Extension : .txt
CreationTime : 2018/04/04 20:30:48
CreationTimeUtc : 2018/04/04 11:30:48
LastAccessTime : 2018/04/04 20:30:48
LastAccessTimeUtc : 2018/04/04 11:30:48
LastWriteTime : 2018/04/04 20:31:17
LastWriteTimeUtc : 2018/04/04 11:31:17
Attributes : Archive
# [Select-Object *]は、各プロパティの内容を確認できる!
型を意識しよう
単純な文字列や数値ではなくオブジェクトを扱う以上、
何よりも大切になってくるのが「型」。
Powershellは自動的にかなり自由な型変換を行ってくれるが、
やっぱりちゃんと型を意識しないと変なことになる。
PS C:\work> $array_1 = @("C:\work\hoge","C:\work\fuga","D:\work\piyo")
PS C:\work> $array_1
C:\work\hoge
C:\work\fuga
D:\work\piyo
PS C:\work> $array_1[1] | Split-Path -Leaf
fuga
# 配列$array_1にパス情報を格納し、2つ目の要素のパスの末端を表示
# 何も問題なし
PS C:\work> $array_2 = $array_1 | Select-String -Pattern "^C:"
PS C:\work> $array_2
C:\work\hoge
C:\work\fuga
PS C:\work> $array_2[1] | Split-Path -Leaf
InputStream
# $array_1からSelect-Stringで"C:"で始まる文字列だけを抽出し、$array_2とする
# 同じようにSplit-Path -Leafすると、InputStreamという謎の文字列が
さて、何が起こったのか。
実はこの[Select-String]コマンドレット、「文字列を検索する」という機能だけど
戻り値は文字列ではなく[MatchInfo]型。
※なんだそりゃ。ややこしい。
grepっぽいのは確かだけど、完全にgrep感覚で使うのは危険かも。
実はこの[Select-String]コマンドレット、「文字列を検索する」という機能だけど
戻り値は文字列ではなく[MatchInfo]型。
※なんだそりゃ。ややこしい。
grepっぽいのは確かだけど、完全にgrep感覚で使うのは危険かも。
詳しく見て行くと次のような感じ。
PS C:\work> $array_1[1].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
# $array_1[1]は[String]型
PS C:\work> $array_2[1].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False MatchInfo System.Object
# $array_2[1]は[MatchInfo]型になっている!!
# プロパティを確認してみよう。
PS C:\work> $array_2[1] | Select-Object *
IgnoreCase : True
LineNumber : 2
Line : C:\work\fuga
Filename : InputStream # 謎の文字列InputStreamの正体はこれだ!
Path : InputStream
Pattern : ^C:
Context :
Matches : {0}
# 余談だけど、MatchInfoのプロパティ FileNameとPathには、
# Select-Stringに引数渡ししたファイルの情報が格納される
# ファイルを渡してないと、このようにInputStreamという文字列が入る
PS C:\work> [String]$array_2[1] | Split-Path -Leaf
fuga
# 型不一致で上手く動かないのならキャストしてしまえ
PS C:\work> Split-Path $array_2[1] -Leaf
fuga
# 理由は分からないけど、[Split-Path]への渡し方がパイプではなく引数だと
# 自動的に[MatchInfo] → [String]に変換されるよう
# 変なの
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
# $array_1[1]は[String]型
PS C:\work> $array_2[1].GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False MatchInfo System.Object
# $array_2[1]は[MatchInfo]型になっている!!
# プロパティを確認してみよう。
PS C:\work> $array_2[1] | Select-Object *
IgnoreCase : True
LineNumber : 2
Line : C:\work\fuga
Filename : InputStream # 謎の文字列InputStreamの正体はこれだ!
Path : InputStream
Pattern : ^C:
Context :
Matches : {0}
# 余談だけど、MatchInfoのプロパティ FileNameとPathには、
# Select-Stringに引数渡ししたファイルの情報が格納される
# ファイルを渡してないと、このようにInputStreamという文字列が入る
PS C:\work> [String]$array_2[1] | Split-Path -Leaf
fuga
# 型不一致で上手く動かないのならキャストしてしまえ
PS C:\work> Split-Path $array_2[1] -Leaf
fuga
# 理由は分からないけど、[Split-Path]への渡し方がパイプではなく引数だと
# 自動的に[MatchInfo] → [String]に変換されるよう
# 変なの
まとめ
テキストベースのシェルに慣れている人がPowershellを触ると、パイプラインの挙動の違いに気持ち悪くなって挫折してしまうケースが多い気がする。
でも、落ち着いてパイプの中身を確認していくようにすると、
すぐに使いこなせるようになる…かもしれない。
0 件のコメント :
コメントを投稿