PowerShell 関連の文法事項
If文の基本的な利用方法
foreachステートメント
テキストファイル読み書き
PowerShell 関連の文法事項
スクリプトファイル
拡張子 .ps2 でスクリプトファイルを作成する.
# コメント
コマンド名の大文字/小文字の別は無視される.
文字コードは Unicode/UTF8
改行コードは windows形式 (CRLF)
実行ポリシーを設定
デフォルトでは安全のためスクリプトファイルは実行禁止になっている.スクリプトを実行する前に実行ポリシーを設定する.
コマンドラインで設定の例.Set-ExecutionPolicy Unrestricted #Unrestricted, RemoteSignedスクリプト実行時に指定の例.PowerShell -ExecutionPolicy Unrestricted -File sample-script.ps1
変数
変数名 $[a-zA-Z0-9_]*
自動変数 $Args$Args[0]
$スコープ修飾子:変数名 で変数のスコープを指定することができる (global、script、local)
$global:x $script:y $local:z
オブジェクト指向
コマンドの結果, 変数の値などはすべてオブジェクト.
Import-Module モジュール/クラスライブラリの読み込み. 使用前に追加で読み込みが必要なクラスもある.Import-Module ActiveDirectory
GetType() 型を得る:$hoge.gettype()
生成 $x = New-Object クラスの完全修飾名(引数,...) $x = New-Object [-typeName] クラスの完全修飾名 [[-argumentList] (object[], ...)]
インスタンス変数参照 :オブジェクト変数.プロパティ名
メソッド呼び出し :オブジェクト変数.メソッド名(引数,...)
静的メンバにアクセス
キャスト$x = [System.Int32] 1.3
メンバを調べる:$hoge | member
適当に書式を整理して表示:Format-List $hoge
匿名メソッドなどの機能
パイプ処理
UNIX のパイプはテキストデータのストリームだが,PowerShellのコマンドレットは、戻り値として .NET Frameworkのオブジェクトを返す文字列ではなくオブジェクトを渡す事に注意.文字列型(String)として意識的に扱うなら良いが?
2>&1 : エラーを標準に束ねる
$_ : パイプライン経由で渡されたオブジェクト
?{...} , Where-Object{...}:
引数のブロックを実行してTrueだった時だけ $_ を次のパイプラインに流す. -match で grep 相当の動作.
.... | ?{ $_.Split(",")[3] -match "12345" } | ....
%{ ... } , Foreach{ ... }:
引数のブロックを実行し結果を次のパイプラインに流す. xargs 相当の動作.
...| %{ ls $_ }
型キャスト
$x = [System.Int32] 1.3 # # 組み込みの型/クラス# type System.Type# byte System.Byte# int System.Int32# long System.Int64# single, float System.Single# double System.Double# decimal System.Decimal# char System.Char# bool System.Boolean# string System.String# array System.Array# xml System.Xml.XmlDocument
ヒアドキュメント
@"~"@ @'~'@
$x=@"abc"@
関数
function スコープ修飾子:関数名 [(引数[=デフォルト値],...)] { 関数の本体 ; $Args[0] ; [return 戻り値] } # 関数を定義 $z=10 function f($x,$y){ $local:z=$Args[0] ; return $x+$y+$z } function g($x,$y){ return $x+$y+$z } # 呼び出し f 1 2 3 g 1 2
名前なし引数は自動変数配列 $Args に格納
配列
要素, 要素, 要素, ...
$x = 1,2,3
@(要素, 要素, 要素, ...)
$x = @(1,2,3,4) Format-List $x Write-Output $x[0] #配列のスライシング $y = x[1..2] $x=@() # empty
ハッシュテーブル/連想配列
@{キー名=値; キー名=値; ...}
$z = @{Alpha="kodama"; Beta=123} Write-Output $z["Alpha"] Write-Output $z.Alpha $z = @{} # empty
条件分岐
if( cond ){ }elseif( cond ){ }else{ }
switch [-wildcard] (){ } switch( val ){ cond { ;break} default{ ;break} } switch -wildcard ($x){ # 正規表現によるswitch "X*" { } }
パイプで選択 Where-Object , ?{} # サイズ>1MB のファイルを探す Get-ChildItem | ?{ $_.Length -ge 1MB -a $_.Name -match "[a-z]{1,}\.log" } # 偶数の和 1..10 | ?{ ($_ % 2) -eq 0 } | Measure -sum ## -average -max -min など
繰り返し
for( ; ; ){} for(初期化式; 終了条件; 増分式){ } for($i=0; $i -lt 3; $i++){ echo $i }
foreach(){} foreach(一時変数 in 配列,コレクション){ } foreach($a in ls){ echo $a.name } foreach($x in @(8,7,6)){ echo $x } # 偶数の和 $sum = 0 ; foreach($i in 1..10) { if( ($i % 2) -eq 0) { $sum += $i } } ; echo $sum
パイプで実行 ForEach-Object{}, foreach{} , %{} # パイプ xargs 相当@(1,2,3) | ForEach-Object{ echo $_ } @(1,2,3) | foreach{ echo $_ } @(1,2,3) | %{ echo $_ } # % は foreach の alias ls | foreach{ echo $_.name } # 偶数の和 $sum = 0 ; 1..10 | ?{ ($_ % 2) -eq 0 } | %{ $sum += $_ } ; echo $sum
while( cond ){ }
do{ }while(cond)
If文の基本構文
If(条件式){
条件式が$Trueの場合の処理
}
Ifの後の丸括弧内の条件式の結果が$Trueとなった場合のみ、波括弧内の処理が行われます。
ElseやElseIfの書き方は以下の通りです。
If(条件式1){
条件式1が$Trueの場合の処理
}ElseIf(条件式2){
条件式2が$Trueの場合の処理
}Else{
条件式1,条件式2とも当てはまらない場合の処理
}
比較演算子
Powershellの比較演算式は以下のようになっています。参考としてVBScriptの場合の比較演算子も併せて紹介します。
Powershell
VBSscript
説明
-eq
=
等しい
-ne
<>
等しくない
-lt
<
より小さい
-le
<=
以下
-gt
>
より大きい
-ge
>=
以上
-like
N/A
ワイルドカードでの比較で一致
-notlike
N/A
ワイルドカードでの比較で一致しない
-match
N/A
正規表現での比較で一致
-notmatch
N/A
正規表現での比較で一致しない
VBScriptの比較演算子と違い、直感的に分かりづらくなっています。以下の略であることを覚えておけば、利用するときに思い出せるかと思います。
eq:equal
ne:notequal
lt:lessthan
le:less than orequal
gt:greaterthan
ge:greater than orequal
また、PowershellではLikeを利用したワイルドカードでの検索や、Matchを利用した正規表現での検索も可能となっています。※VBSもオブジェクトを読み込めば正規表現は利用可能です。
比較演算子を利用した条件分岐の例
入力された年齢が6才以下で「未就学児」、20才未満で「未成年」、それ以外は「大人」と表示するスクリプトです。
# C:\Sample.ps1
[int]$age=Read-Host"年齢を入力してください"
If($age-le6){
Write-Host"未就学児"
}ElseIf($age-lt20){
Write-Host"未成年"
}Else{Write-Host"成人"
}
PS C:\>.\Sample1.ps1年齢を入力してください: 2未就学児PS C:\>.\Sample1.ps1年齢を入力してください: 19未成年PS C:\>.\Sample1.ps1年齢を入力してください: 20成人
条件式は上から順に評価されます。上記例で条件式1と条件式2を入れ替えた場合、2才と入力すると未成年と判定されます。
ワイルドカード、正規表現を利用した条件分岐の例
入力した文字列に"test"という文字列が含まれているかチェックするスクリプトです。
# C:\Sample2.ps1
$Text=Read-Host"文字を入力してください"
If($Text-Like"*test*"){# 文中にtestが含まれる場合(前後にワイルドカード)
Write-host"testが含まれます。(-Like)"
}If($Text-match"^test"){# 行頭にtestが含まれる場合
Write-host"testが含まれます。(-match)"
}
PS C:\>.\Sample2.ps1
文字を入力してください: これはtestです。
testが含まれます。(-Like)
PS C:\>.\Sample2.ps1
文字を入力してください: testです。
testが含まれます。(-Like)
testが含まれます。(-match)
大文字、小文字の判断
利用するケースは稀だと思いますが、ハイフンの後にcを付与すると大文字と小文字の区別が可能となります。 例)-ceq (大文字・小文字も含めて等しいか)
PS C:\>$a="Test"
PS C:\>If($a-eq"TEST"){Write-Host"一致している"}else{Write-Host"一致していない"}
一致している
PS C:\>If($a-ceq"TEST"){Write-Host"一致している"}else{Write-Host"一致していない"}
一致していない
論理演算子
複数条件の組み合わせによる条件分岐をする場合は、論理演算子を利用します。こちらも参考としてVBScriptの場合の論理演算子も併せて紹介します。
Powershell
VBSscript
説明
-and
And
論理積
-or
Or
論理和
-xor
Xor
排他的論理和
-not
Not
否定
PowershellもVBScriptもほぼ同じですね。他の言語ではAndを「&&」、Orを「||」と表記するものもあります。
Aという条件式とBという条件式があった場合、以下の条件に合致した場合、$Trueを返します。
-and:Aの条件式がTrue、かつ、Bの条件式がTrue、かつ、Bの条件式がTrue
-or:Aの条件式、または、Bの条件式のどちらか、または両方が$True
-xor:Aの条件式、または、Bの条件式のどちらかが$True
-not:条件式が$False
論理演算子を利用した条件分岐の例
年齢と性別を入力し、法律的に結婚可能か判断するスクリプトです。
# C:\Sample3.ps1
[int]$age=Read-Host"年齢を入力してください"
[string]$gender=Read-Host"性別を入力してください"
If($age-ge16-and$gender-eq"女"){
Write-Host"結婚できます。"
}ElseIf($age-ge18){
Write-Host"結婚できます。"
}Else{
Write-Host"結婚できません。"
}
PS C:\>.\Sample3.ps1
年齢を入力してください: 16
性別を入力してください: 女
結婚できます。
PS C:\>.\Sample3.ps1
年齢を入力してください: 16
性別を入力してください: 男
結婚できません。
PS C:\>.\Sample3.ps1
年齢を入力してください: 18
性別を入力してください: 男
結婚できます。
上記例は少しわかりやすくするためにElseIfを利用しましたが、以下のように組み替えることができます。
[int]$age=Read-Host"年齢を入力してください"
[string]$gender=Read-Host"性別を入力してください"
If(($age-ge16-and$gender-eq"女")-or$age-ge18){# 論理演算子を組み合わせ
Write-Host"結婚できます。"
}Else{
Write-Host"結婚できません。"
}
条件式を日本語に起こすと「年齢が16才以上、かつ、性別が女、もしくは年齢が18歳以上」となります。
論理演算子を組み合わせることで、複雑な条件分岐も可能となりますが、条件式が正しく評価されることを確実に確認しないとバグの原因ともなるので注意しましょう。
コマンドレットによる条件分岐
ここまで演算子を利用した条件分岐を紹介してきましたが、各種コマンドレットで$Trueを返すものであれば条件分岐に利用できます。
よく使いそうな2つの例を紹介します。
ファイルの有無で条件分岐
ファイルの有無を確認するコマンドレットである「Test-Path」の結果で条件分岐します。C:\test.txtファイルを事前に作成してあります。
PS C:\>If(Test-Path"C:\test.txt"){Write-Host"ファイルがあります。"}Else{Write-Host"ファイルはありません。"}
ファイルがあります。
PS C:\>If(Test-Path"C:\test2.txt"){Write-Host"ファイルがあります。"}Else{Write-Host"ファイルはありません。"}
ファイルはありません。
Pingで応答がある場合の条件分岐
Pingと同等の機能を有するコマンドレットである「Test-Connection」の結果で条件分岐します。TestServerは稼働しているサーバーと仮定します。
PS C:\>If(Test-ConnectionTestServer){Write-Host"疎通確認が取れました"
}疎通確認が取れました
以上です。
概要
少し分かりづらいのですが、Powershellにおけるforeachによるループは2種類あり、それぞれ構文の書き方や動作が異なります。
foreachステートメント
1つ目がforeachステートメントです。以下簡単な例です。
実行結果
PS C:\>$array= @(1,2,3,4,5)
PS C:\>foreach($a in $array){
>>Write-Host$a
>>
}
1
2
3
4
5
基本的には配列に格納されている値やオブジェクトを一つずつ処理する際に使用します。変数のBaseTypeがSystem.Arrayとなっている変数に対して利用可能です。
実行結果
PS C:\>$array.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
ForEach-Objectコマンドレット
2つめはForEach-Objectコマンドレットです。以下、簡単な例です。
実行結果
PS C:\> @(1,2,3,4,5) |ForEach-Object{Write-Host$_
}
1
2
3
4
5
PS C:\>@(1,2,3,4,5) |foreach{Write-Host$_
} #同じ結果が得られる
PS C:\>@(1,2,3,4,5) |%{Write-Host$_
}#同じ結果が得られる
基本的にはパイプでオブジェクトを受け取って処理を実行します。こちらについても受け取るオブジェクトが配列である必要があります。
使い分け
非常にややこしいのは、ForEach-ObjectのAlias(別名)にforeachという名前があるためです。
実行結果
PS C:\>Get-Alias|Where-Object{$_.DisplayName-like"*ForEach-Object"
}
CommandType Name Version Source
----------- ---- ------- ------
Alias % -> ForEach-Object
Alias foreach -> ForEach-Object
ForEach-Objectは既定で別名として「foreach」「%」が登録されています。そのため、「foreach」で動く構文が2種類存在してしまっています。
どちらを利用するかの判断ポイントは大まかに以下の2つとなります。
パイプを利用するかどうか
処理するデータの量と求められるスピード、メモリ容量
まずは、パイプを利用するかどうかです。foreachステートメントはパイプでオブジェクトの受け取りが不可のため、パイプ処理を利用する場合はForEach-Objectを利用する必要があります。とはいえコードの書き方次第でパイプは利用しない構成にも出来るので、これは好みの問題となります。
もうひとつは内部処理による違いです。foreachステートメントとForEach-Objectではループでのメモリの利用方法に差異があります。
具体的にはforeachステートメントは受け取った配列の要素を全てメモリに読み込んでから処理が実行されます。そのため、
処理内容によっては大量のメモリを消費する
可能性がありますが、
その分高速になる
可能性があります。
一方、ForEach-Objectでは受け取ったオブジェクトを一つずつメモリに読み込んで処理が実行されます。そのため、大量のオブジェクトがあると、処理ごとにオブジェクトの生成と破棄が行われるため、
利用するメモリの量は少ない
ですが
処理が遅くなる
可能性があります。
簡単なスクリプトであればあまり考慮する必要はないですが、大量のデータを処理するスクリプトなどでは考慮が必要になってきます。状況によって判断、というしかありません。
foreachステートメントの使い方
foreachステートメントの使い方です。
構文:
foreach( [変数] in [配列] ) {
処理内容
}
配列の要素をひとつずつ変数に格納して処理を実行します。
以下、foreachステートメントを利用した例です。特定のフォルダ内(この例ではC:\Tmp)のファイルに、特定の文字列が含まれているか検索するスクリプトです。
C:\Tmp\aaa.txt
⇒ファイルの中身:このファイルはテストファイルです。
C:\Tmp\bbb.txt
⇒ファイルの中身:このファイルはテキストファイルです。
C:\Tmp\ccc.txt
⇒ファイルの中身:このファイルはテストです。
# C:\foreach.ps1
$Array=(Get-ChildItem-Path C:\Tmp-File).FullName#C:\tmpの中のファイル一覧を$Arrayに格納
foreach($ain$Array){#Arrayの要素を変数$aに格納して処理を実行
If(Select-String-Path$a-Pattern"テスト"-Quiet){#テストという文字列が含まれていればTrueとなる
Write-Host$aにはテストという文字列が含まれています。
}
}
実行結果
PS C:\>C:\foreach.ps1
C:\Tmp\aaa.txt にはテストという文字列が含まれています。
C:\Tmp\ccc.txt にはテストという文字列が含まれています。
ForEach-Objectの使い方
ForEach-Objectの使い方です。
構文:
[配列オブジェクト] または [各種コマンドレット] | ForEach-Object{ 処理内容
}
foreachステートメントで利用した例をForEach-Objectでも書いてみます。
実行結果
PS C:\> (Get-ChildItem-PathC:\Tmp-File).FullName |ForEach-Object{If(Select-String-Path$_-Pattern"テスト"-Quiet){Write-Host$_にはテストという文字列が含まれています。}}
C:\Tmp\aaa.txt にはテストという文字列が含まれています。
C:\Tmp\ccc.txt にはテストという文字列が含まれています。
一行で書けました。
「(Get-ChildItem -Path C:\Tmp -File).FullName」というGet-ChildItemコマンドレットのFullNameプロパティを取得すると、この例では以下の値が取得されます。
実行結果
PS C:\tmp> (Get-ChildItem-PathC:\Tmp-File).FullName
C:\Tmp\aaa.txt
C:\Tmp\bbb.txt
C:\Tmp\ccc.txt
この3つ要素が格納されている配列を一つずつパイプの先に渡します。
渡した先では「$_」という特殊な変数に格納されるので、この変数に対してファイルの中身を検索する処理を追加しています。
また、ForEach-Objectには-Beginや-Endといったオプションがあり、処理が実施される前または処理が実施された後に実行する処理を指定することもできます。但し、このオプションを使うくらいならforeachステートメントの方が他の言語に通ずる書き方でもあるため、可読性が高いです。本記事の前半で紹介したよう、処理のスピードやメモリについての制約がなければ、foreachステートメントで書くのがいいかと思います。
Select-Stringコマンドレット
例で特定フォルダ内のファイルに、特定の文字列が含まれているか検索するスクリプトを紹介しましたが、同じようなことがSelect-Stringコマンドレットで可能です。
実行結果
PS C:\>Select-String-PathC:\tmp\*.txt-Patternテスト
aaa.txt:1:このファイルはテストファイルです。
ccc.txt:1:このファイルはテストです。
テキストファイル読み書き
スクリプトと同じディレクトリにある"test.txt"を読み込んで表示。
# 実行中のパス取得/移動
$path = Split-Path -Parent
$MyInvocation.MyCommand.PathSet-Location
$path
# ファイル読み込み
# StreamReaderのコンストラクタに直接 「$path + "\test.txt"」を入力するとエラーになるので分ける
$fileName = $path + "\test.txt"
$file = New-Object System.IO.StreamReader($fileName, [System.Text.Encoding]::GetEncoding("sjis"))
while (($line = $file.ReadLine()) -ne $null)
{
Write-Host($line)
}
Write-Host("")
$file.Close()
# 終了
Write-Host("終了")
スクリプトと同じディレクトリに"test2.txt"を作成して上書き。
# 実行中のパス取得/移動
$path = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $path
# ファイル書きこみ
# StreamReaderのコンストラクタに直接 「$path + "\test.txt"」を入力するとエラーになるので分ける
$fileName = $path + "\test2.txt"
$file = New-Object System.IO.StreamWriter($fileName, $false, [System.Text.Encoding]::GetEncoding("sjis"))
$file.WriteLine("あいうえお")
$file.WriteLine("かきくけこ")
$file.WriteLine("さしすせそ")
Write-Host("")
$file.Close()
# 終了
Write-Host("終了")