Windows PowerShell シェル・スクリプト・プログラミング

Windows PowerShell シェル・スクリプト・プログラミング


サイト全体の目次

稿


稿

本項の解説は totobon の備忘録的要素が強いため、手取り足取りの解説はしていません。すでに C# や C++/CLI といった .net プログラミングの経験がある人に直観的に分かるような解説を目的としています。

また、以下、PowerShell を PS と表記します。

全ては .net ベースのオブジェクト

PS では全てのものが .net framework のオブジェクトとして扱われます。例えば、「echo」コマンドは「String」型を返すので、Length プロパティによって文字列の長さを得ることができますし、GetType() メソッドを使えば、オブジェクトに関する情報も得ることができます.。ただし、「cp」や「mv」など「null」を返すコマンドでは、このようなプロパティやメソッドを使うことができません。

PS D:\Works> (echo "hello, world.").Length
13
PS D:\Works> (echo "hello, world.").GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object
			

このようなコマンドを「コマンドレット」と呼びますが、本項ではとくに断りがなければ「コマンド」と呼びます。

基本事項

大文字と小文字

PS では大文字と小文字は区別されません。

1行に1文だけであれば、文の終わりを示す区切り記号はいりませんが、C# や C++/CLI などと同様、「;」で文を区切ることができます.。以降、1行に1文の場合でも「;」を付けます。

PS D:\Works> (echo "hello, world.").Length; (echo "hello, world.").GetType();
13

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     String                                   System.Object
				

コメント

PS では、「#」から行末までがコメントとして扱われます。

スクリプト

PS では、より強力なスクリプト機能が使用できます。もっとも簡単なスクリプトは単にコマンドを羅列したもの.ですが、より高度な機能として、C# や C++/CLI に類似した制御文や、.net framework のクラスなどが使用できます.

PS D:\Works> echo "hello," > "test1.txt"
PS D:\Works> echo "world." > "test2.txt"
PS D:\Works> (cat "test1.txt") + (cat "test2.txt") > "test3.txt"
PS D:\Works> cat "test3.txt"
hello,world.
#3行目では cat が String 型オブジェクトを返すため、「+」による連結を行っています。
			
PS D:\Works> for([Int32] $i = 0; $i -lt 5; $i++){ echo $i.ToString().PadLeft(3, '0'); }
000
001
002
003
004
			

スクリプトの実行

直接入力


スクリプトのもっとも簡単な実行方法は、PS 上に直接入力してしまうやり方です。前述の for 文も、行を分けて入力・実行することができます。入力の終了は、空行で、空行が入力された時点でスクリプトが実行可能であれば実行します.。スクリプトが完全でなければ入力の継続を促すか、エラーを表示します。

PS D:\Works> for([Int32] $i = 0; $i -lt5; $i++){
>>     echo $i.ToString().PadLeft(3, '0');
>> }
>>
000
001
002
003
004
				

スクリプトファイルとして

もう一つの方法は、スクリプトをファイルに書いて、このファイルを実行する方法です。まずは、.のスクリプトファイルをサンプルとして作っておきます。

for([Int32] $i = 0; $i -lt5; $i++){
	echo $i.ToString().PadLeft(3, '0');
}
[Console]::WriteLine("処理が終了しました。Enter を押してください。");
[Console]::ReadLine();
				

このファイルをスクリプトとして実行する、とっさに思いつく方法は拡張子を「.bat」や「.cmd」として実行する方法ですが、これらの拡張子は旧来のコマンドプロンプトに関連付けされているので、不適当です。

PS スクリプトにつけられる最も一般的な拡張子の一つは「.ps1」 (*1) ですので、これを PowerShell に関連付けましょう (*2) 。PowerShell の場所ですが、通常は「C:\Windows\system32\WindowsPowerShell」の中に、バージョンごとに分けられて入っています。関連づけが終わったら、先ほど作った test.txt の拡張子を .ps1 へ変更するのも忘れないでください。

「ps1」の「1」は PowerShell のバージョンが「1」であることを意味します。
関連付けについては、別の一般的な資料を参照してください。

また、スクリプトを実行する際に注意しないといけないのは、カレントディレクトリにスクリプトファイルがある場合でも、UNIX 系 OS のように「./」としてディレクトリ位置を明示してやらなければならない点です.

PS D:\Works> ./test
				

しかし、このままではまだ動きません。実際、実行してみると警告メッセージ.が出て、すぐに終了してしまいます。

スクリプトの実行がシステムで無効になっているため、ファイル D:\Works\test.ps1
を読み込めません。詳細については、「get-help about_signing」と入力してヘルプを
参照してください。
				

そこで、「Set-ExecutionPolicy」コマンドを、引数「RemoteSigned」付きで実行.し、スクリプトの実行を有効にしておきます (*3) 。このとき、Vista 以上の OS であれば、PS を管理者として実行 (*4) します。

Set-ExecutionPolicy コマンドの詳細は、PS 上で「get-help Set-ExecutionPolicy -detailed」として得ます。
PowerShell のアイコンを右クリックして「管理者として実行」を選択。
PS D:\Works> Set-ExecutionPolicy RemoteSigned
				

スクリプトをコマンドとして使う

自分で作ったスクリプトは、コマンドのように使うことができます。スクリプトのあるディレクトリにパスを通しておけば、「./」など無しに、スクリプトファイルの名前だけで使用することができます。自分のスクリプト集をどこかのディレクトリに用意して、そこにパスを通しておくとよいでしょう。

パスの通しかた

パスはシステムのプロパティから環境変数を設定することで永続的に通すことができますが、必要ならば PS の実行ごとに参照.、設定.することもできます。

PS D:\Works> $env:Path
				
PS D:\Works> $env:Path += ";D:\Works\Scripts"
				

設定は、「$env:Path」がつまるところ文字列に過ぎないため、それに必要なパスを連結しています。追加するパスの頭に、パスの区切りである「;」を書き忘れないように気をつけましょう。

ここから先は、具体的なサンプルを提示しますので、直観でご理解ください。時間ができたら詳細な説明をつけていきます。

また、ここから先のサンプルコードは、特に断りがなければそのままコピー&ペーストで実行可能です。

機能

型、クラス、変数

.net framework の型やクラスを使用して変数を宣言できます。

[String] $str = "hello, world.";
[Int32] $n = 123;
echo $str $n;
			

名前空間

名前空間は「System.」を省略することができます。

[System.Int32] $n1 = 123;
[Int32] $n2 = 456;
echo $1;
echo $2;
			

オブジェクト生成

オブジェクトを生成するときは「new-object」を使用します。C# の new や C++/CLI の gcnew に相当する機能です。

[String] $str1 = new-object String("hello,");
echo $str1;
echo "------";

[System.String] $str2 = new-object System.String("world.");
echo $str2;
echo "------";

[Int32] $num = new-object Int32;
echo $num;
			

型変換(キャスト)

[Double] $f = 123.456;
echo $f;
[Int32] $n1 = $f -as [Int32];
echo $n1;
[Int32] $n2 = [Int32]$f;
echo $n2;
			

算術演算

[Int32] $n1 = 456;
[Int32] $n2 = 123;
echo ($n1 + $n2);
echo ($n1 - $n2);
echo ($n1 * $n2);
echo ($n1 / $n2);
echo ($n1 % $n2);
echo "------";
echo $n1;
$n1 += 123;
echo $n1;
echo "------";
echo $n1;
$n1 -= 123;
echo $n1;
echo "------";
echo $n1;
echo (++$n1);
echo ($n1++);
echo $n1;
echo "------";
echo $n1;
echo (--$n1);
echo ($n1--);
echo $n1;
#除算は商ではなく浮動小数点数となる点に注意。
			

プロパティ、メソッドの呼び出し

[String] $str = "hello, world.";
[Int32] $n1 = $str.Length;
[Int32] $n2 = $str.get_Length();
echo $str;
echo $n;

$str = $str.ToUpper();
echo $str;

$str = "123";
$n = [Int32]::Parse($str);
$n += 456;
echo $n;
#一部、プロパティの実装されていないクラスがあります。
#例えば XmlDocument の InnerText などです。
#そのような場合には get_PropertyName() や
#set_PropertyName() を使用します。
			

標準入出力

[String] $str1 = [Console]::ReadLine();
[String] $str2 = [Console]::ReadLine();
echo "------";
echo $str1 $str2;
echo "------";
[Console]::WriteLine($str1 + $str2);
			

エスケープ文字

[Console]::Write("hello,`t`#world.`r`n");
			

配列

[Int32[]] $intArray1 = 1,2;
[String[]] $strArray1 = "abc", "def", "ghi";
[Console]::WriteLine($intArray1[0].ToString() + "," + $intArray1[1].ToString());
[Console]::WriteLine($strArray1[0] + "," + $strArray1[1] + "," + $strArray1[2]);

[Int32[]] $intArray2 = new-object Int32[](2);
$intArray2[0] = 3;
$intArray2[1] = 4;
[String[]] $strArray2 = new-object String[](3);
$strArray2[0] = "jkl";
$strArray2[1] = "mno";
$strArray2[2] = "pqr";
[Console]::WriteLine($intArray2[0].ToString() + "," + $intArray2[1].ToString());
[Console]::WriteLine($strArray2[0] + "," + $strArray2[1] + "," + $strArray2[2]);
			

変数が一つの値(オブジェクト)である場合は、長さ1の配列とみなしてキャストできますが、配列の場合は、それを一つの値(オブジェクト)と見なすことはできません。

[String] $str = "hello, world.";
[Console]::WriteLine("------ 戻り値 ------");
[Console]::WriteLine($str);
[String[]] $strArray = [String[]]$str;
[Console]::WriteLine("------ 戻り値のリスト ------");
foreach($s in $str){
	[Console]::WriteLine($s);
}
			

論理演算、条件分岐

[Console]::Write("数1: ");
$str1 = [Console]::ReadLine();
[Console]::Write("数2: ");
$str2 = [Console]::ReadLine();

$n1 = [Int32]::Parse($str1);
$n2 = [Int32]::Parse($str2);

if($n1 -lt $n2){ [Console]::WriteLine($n1.ToString() + "<" + $n2.ToString()); }
if($n1 -le $n2){ [Console]::WriteLine($n1.ToString() + "<=" + $n2.ToString()); }
if($n1 -ge $n2){ [Console]::WriteLine($n1.ToString() + ">=" + $n2.ToString()); }
if($n1 -gt $n2){ [Console]::WriteLine($n1.ToString() + ">" + $n2.ToString()); }
if($n1 -eq $n2){ [Console]::WriteLine($n1.ToString() + "==" + $n2.ToString()); }
if($n1 -ne $n2){ [Console]::WriteLine($n1.ToString() + "!=" + $n2.ToString()); }

[Console]::WriteLine("------");

if($n1 -lt $n2 -or $n1 -eq $n2){ [Console]::WriteLine($n1.ToString() + "<=" + $n2.ToString()); }
if($n1 -gt $n2 -or $n1 -eq $n2){ [Console]::WriteLine($n1.ToString() + ">=" + $n2.ToString()); }

[Console]::WriteLine("------");

if($n1 -le $n2 -and $n1 -ne $n2){ [Console]::WriteLine($n1.ToString() + "<" + $n2.ToString()); }
if($n1 -ge $n2 -and $n1 -ne $n2){ [Console]::WriteLine($n1.ToString() + ">" + $n2.ToString()); }

[Console]::WriteLine("------");

if($n1 -lt 0){
	[Console]::WriteLine($n1.ToString() + "<0");
}elseif($n1 -gt 0){
	[Console]::WriteLine($n1.ToString() + ">0");
}else{
	[Console]::WriteLine($n1.ToString() + "==0");
}

[Console]::WriteLine("------");

if($true){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }
if(!$true){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }
if($false){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }
if(!$false){ [Console]::WriteLine("true"); }else{ [Console]::WriteLine("false"); }

[Console]::WriteLine("------");

[String] $str = "hello, world.";
if($str -is [String]){ [Console]::WriteLine("str==String"); }else{ [Console]::WriteLine("str!=String"); }
if($str -isnot [String]){ [Console]::WriteLine("str!=String"); }else{ [Console]::WriteLine("str==String"); }

[Console]::WriteLine("------");

[Int32] $num = 123;
if($num -is [String]){ [Console]::WriteLine("num==String"); }else{ [Console]::WriteLine("num!=String"); }
if($num -isnot [String]){ [Console]::WriteLine("num!=String"); }else{ [Console]::WriteLine("num==String"); }
			

switch

[String] $month = [Console]::ReadLine();
$month = $month.ToLower();

switch($month){
	"january" { [Console]::WriteLine("1月"); }
	"february" { [Console]::WriteLine("2月"); }
	"march" { [Console]::WriteLine("3月"); }
	"april" { [Console]::WriteLine("4月"); }
	"may" { [Console]::WriteLine("5月"); }
	"june" { [Console]::WriteLine("6月"); }
	"july" { [Console]::WriteLine("7月"); }
	"august" { [Console]::WriteLine("8月"); }
	"september" { [Console]::WriteLine("9月"); }
	"october" { [Console]::WriteLine("10月"); }
	"november" { [Console]::WriteLine("11月"); }
	"december" { [Console]::WriteLine("12月"); }
	default { [Console]::WriteLine("該当なし"); }
}
			

ループ

[Int32[]] $intArray = 1,1,2,3,5,8,13;

[Int32] $i = 0;
while($i -lt $intArray.Length){
	[Console]::Write($intArray[$i].ToString() + " ");
	$i++;
}
[Console]::Write("`r`n");

[Console]::WriteLine("------");

for([Int32] $i = 0; $i -lt $intArray.Length; $i++){
	[Console]::Write($intArray[$i].ToString() + " ");
}
[Console]::Write("`r`n");

[Console]::WriteLine("------");

foreach($n in $intArray){
	[Console]::Write($n.ToString() + " ");
}
[Console]::Write("`r`n");
			

コマンドレット

スクリプト内でコマンドレットの結果をオブジェクトとして使うことができます。たとえば、「dir」は「Object」の配列を返し、それらオブジェクトは「DirectoryInfo」か「FileInfo」です。

[Object[]] $list = dir;
for([Int32] $i = 0; $i -lt $list.Length; $i++){
	if($list[$i] -is [IO.DirectoryInfo]){
		[IO.DirectoryInfo] $di = $list[$i];
		[Console]::WriteLine($di.Name + " はディレクトリ。");
	}else{
		[IO.FileInfo] $fi = $list[$i];
		[Console]::WriteLine($fi.Name + " はファイル。");
	}
}
			

関数

基本形

function func()
{
	echo "hello, world."
	echo "------"
	echo "hello," echo " world."
	echo "------"
	echo "hello,"; echo " world.";
}

func
			

可変長引数

関数に引数を与えるときは

FunctionName arg1 arg2 ...
				

のような形式をとります。また、引数の受け取り方を特に指定していない場合は、全ての引数が文字列の配列「$args」に格納されます。

function func()
{
	[Console]::WriteLine("------ func ------");
	foreach($str in $args){
		[Console]::WriteLine($str);
	}
}

func abc def ghi jkl
			

引数

引数を指定した変数で受け取りたい場合は、以下のようにします。

function func([String] $arg1, [String] $arg2)
{
	[Console]::WriteLine("====== func ======");
	[Console]::WriteLine("arg1=" + $arg1 + ", arg2=" + $arg2);
	[Console]::WriteLine("---他の引数---");
	foreach($str in $args){
		[Console]::WriteLine($str);
	}
}

func abc def ghi jkl;
func ghi -arg2 abc jkl -arg1 def;
func ghi --arg2 abc jkl --arg1 def;
			

参照渡し

function func([ref] $arg1, [ref] $arg2)
{
	$arg1.Value = "hello,";
	$arg2.Value = "world.";
}

[String] $str1 = "abc";
[String] $str2 = "def";
[Console]::WriteLine("str1=" + $str1 + ", str2=" + $str2);
func ([ref]$str1) ([ref]$str2);
[Console]::WriteLine("str1=" + $str1 + ", str2=" + $str2);
			

戻り値

function func()
{
	return "OK";
}

[Console]::WriteLine("====== 戻り値を受け取らない呼び出し ======");
func;
[Console]::WriteLine("====== 戻り値を捨てる呼び出し ======");
[void](func);
[Console]::WriteLine("====== 戻り値を受け取る呼び出し ======");
[String] $ret = func;
[Console]::WriteLine("ret=" + $ret);
			

関数内部で呼んだメソッド等の戻り値の蓄積

関数内部で別のメソッドや関数、コマンドを呼んだときにその戻り値を受け取らないと、それらが全て配列に納められて返されます。

ただし、例えば [Console]::WriteLine による標準出力などは、これによってオブジェクト(文字列)が返されるわけではないので、別扱いです。

function subfunc()
{
	return "456";
}

function func()
{
	$n = 123;
	$n.ToString();
	subfunc;
	echo "789";
	[Console]::WriteLine("999");
	return "000";
}

[String[]] $ret = func;
[Console]::WriteLine("------ 戻り値のリスト ------");
foreach($s in $ret){
	[Console]::WriteLine($s);
}
			

関数内部で呼んだメソッド等の戻り値の破棄

関数内部でメソッドを呼んでも、その戻り値を受け取るか、[Void] で破棄すれば、それらは返りません。

また、関数内部で呼んだのが別の関数やコマンドならば、パイプで「Out-Null」に落とせば、それらも返りません。

function subfunc()
{
	return "456";
}

function func()
{
	$n = 123;
	[Int32] $n0 = $n.ToString();
	[Void] $n.ToString();
	subfunc | Out-Null;
	echo "789" | Out-Null;
	return "000";
}

[String] $ret = func;
[Console]::WriteLine("------ 戻り値 ------");
[Console]::WriteLine($ret);
			

関数内部で配列型の戻り値を返す関数等を複数回呼んだ場合の戻り値

関数内部で呼んだ別の関数やコマンドが配列を返す場合に、その関数やコマンドを複数回よんだとしても、関数からの戻り値は配列の配列ではなく、ただの配列になります。

function subfunc()
{
	echo "123";
	echo "456";
}

function func()
{
	subfunc;
	subfunc;
}

[String[]] $ret = func;
[Console]::WriteLine("------ 戻り値のリスト ------");
foreach($s in $ret){
	[Console]::WriteLine($s);
}
			

スクリプトの引数(パラメータ)

スクリプトファイルは、そのファイル全体で前述したような関数の性質を持ちます。直観的には、ファイル名が関数の名前に相当します。

このとき、スクリプトファイルで引数(パラメータ)を受け取るには、ファイルの最初で param を使います.。もちろん、可変長引数の性質もそのまま引き継いでいます。このスクリプトを「paramtest.ps1」という名前で保存した後、.のようなテストコードを実行してみると、スクリプトの挙動が関数と同じであることが確認できます。

param([String] $arg1, [String] $arg2)

function func([String] $arg1, [String] $arg2)
{
	return $arg1 + $arg2;
}

[Console]::WriteLine("====== paramtest ======");
[Console]::WriteLine("arg1=" + $arg1 + ", arg2=" + $arg2);
[Console]::WriteLine("---他の引数---");
foreach($str in $args){
	[Console]::WriteLine($str);
}
[Console]::WriteLine("====== func の結果 ======");
[Console]::WriteLine((func $arg1 $arg2));
			
./paramtest abc def ghi jkl;
./paramtest ghi -arg2 abc jkl -arg1 def;
./paramtest ghi --arg2 abc jkl --arg1 def;
			

アセンブリの読み込み

標準で使用できない .net framework のクラスは、アセンブリを読み込むことで使用できます。

[Void][Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Drawing.Rectangle] $rect = new-object System.Drawing.Rectangle(0, 0, 10, 20);
echo $rect.Width;
echo $rect.Height;
		

変数のスコープ

例えば C++/CLI や C# ではブロック( { ~ } )を使うことで変数の有効範囲を.のように制御することができます。

この例では、(1)と(2)の内部で宣言した「n」は、その外側(main の直後)で宣言した「n」よりも優先され、かつ、宣言されたブロック内でのみ有効なのに対し、(3)は外側で宣言した「n」がそのまま使用されていることを示しています。

using namespace System;

int main()
{
	int n = 1;

	{//(1)
		int n = 10;
		n++;
		Console::WriteLine(n);
	}

	{//(2)
		int n = 20;
		n++;
		Console::WriteLine(n);
	}

	{//(3)
		n++;
	}

	Console::WriteLine(n);

	return 0;
}
			

これに対し PS では、上記例とは挙動の異なる2種類のブロックが存在し、一つ目はブロックの外側にまったく影響を及ぼさないケースで、二つ目はブロックの外側に、完全に影響を与えるケースです。

まず、一つ目の例を.に載せます。

この例では、ブロックの最初に「&」を置いており、これによってブロックの外側にまったく影響を及ぼさないブロックであることを示します。実行してみると分かりますが、(3)のブロックの中の「$n」は、ブロックの外側に影響を及ぼしません。本項では説明しませんでしたが、PS は JavaScript などと同様、変数の宣言を省略出来ます。従って、(3)内は「&」によってブロック外と独立しているため、この中の「$n」は外とは独立した(新しく宣言された)変数として扱われることになり、最初の(main 直後の)「$n」は最後まで「1」のままとなります。

[Int32] $n = 1;

&{#(1)
	[Int32] $n = 10;
	$n++;
	[Console]::WriteLine($n);
}

&{#(2)
	[Int32] $n = 20;
	$n++;
	[Console]::WriteLine($n);
}

&{#(3)
	$n++;
}
			

次に二つ目の例を.に載せます。

この例では、ブロックの最初に「.」を置いており、これによってブロックの外側に完全に影響を与えるブロックであることを示します。このケースでは、(1)と(2)のブロック内で新たに変数「$n」を宣言しているにもかかわらず、まるでその宣言が無いかのように、ブロック外(main 直後)の変数「$n」が操作されます。このため、最後の出力は(2)内で「20」が代入されてインクリメントされ(「$n」は「21」)、さらに(3)でインクリメントされて「22」となります。

[Int32] $n = 1;

.{#(1)
	[Int32] $n = 10;
	$n++;
	[Console]::WriteLine($n);
}

.{#(2)
	[Int32] $n = 20;
	$n++;
	[Console]::WriteLine($n);
}

.{#(3)
	$n++;
}

[Console]::WriteLine($n);
			

読み込み中・・・
10秒待っても表示が変わらない場合、次の理由が考えられます。
・Javascript が無効になっています。
・検索エンジンのキャッシュを見ています。
サイトホームへ / 上位ページへ / ページトップへ / PAROFトレンドショッピングへ
Copyright (C) 2010 totobon all right reserved.