2010年5月3日月曜日

Windows Power Shellにおいて、バイト数単位で文字列を切り取る関数を作る



ゴールデンウィーク真っ只中、皆様いかがお過ごしでしょうか。
私たちの場合は、超早朝行動・午前中行動基本・高速道路完全スルーで、今のところ悲劇的な混雑はギリギリで回避成功中。でも終盤戦は結構きつそう・・・。

さて、今回の記事ですが、Windows Power Shellにおいてバイト数単位で文字列を切り取る関数を作ってみます。
基本方針は、今までSQL-Server 2008向けにSQLCLR(C#言語)で作っていた関数を、Windows Power Shellに移植するというものです。

実行例などは、スクリーンショットを参照いただければと思います。

Windows Power Shellは、本当にパッと見は、なんとなーくC#言語っぽい感じもしますが、当然のことながら「Windows Power Shell用スクリプト言語」なので全然別物になってます。
しかし、何とか書き換える事が可能になっているというのが嬉しいところ。
ただ、比較演算子がLinuxのシェルスクリプト(bash)方式で、最初は馴染みにくいんじゃないかなと思います。

a==bならa -eq b
a!=bならa -ne b
a>bなら a -gt b
a<bならa -lt b
a>=bならa -ge b
a<=bならa -le b


正直に言って、最初Linuxのシェルスクリプトをやった時、出会ったこの仕様は涙目でした・・・。
ちなみに論理演算子もこんな感じで、一例を挙げますと・・・
&&は-and
||は-or

となります。

■コード■
(テキストエディタで書けます)
(インデントは消えています)
(64bit Windows 7用です)

#set-executionpolicy remotesignedで実行して下さい。

#--------------------------------------
#バイト単位での文字列抽出
#(パラメータ説明)
#p_value : 文字列
#p_start : 開始位置(0スタート)
#p_length: 長さ
#--------------------------------------
function global:SubStringB(
[string]$p_value = "",
[Int32]$p_start = 0,
[Int32]$p_length = 0
)
{
#----------------------------
#変数定義
#----------------------------
#結果格納エリア
[string]$str_result = "";
#----------------------------
#処理
#----------------------------
try
{
$str_result = $(fnc_Core_StringB $p_value $p_start $p_length);
}
catch [Exception]
{
$Error[0];
}
return $str_result;
}

#--------------------------------------
#バイト単位で右側から文字列を切り出し
#(パラメータ説明)
#p_value : 文字列
#p_length: 長さ
#--------------------------------------
function global:RightB(
[string]$p_value = "",
[Int32]$p_length = 0
)
{
#----------------------------
#変数定義
#----------------------------
#結果格納エリア
[string]$str_result = "";
#位置
[Int32]$int_start_location = 0;
#----------------------------
#変数定義
#----------------------------
try
{
$int_start_location =
[Text.Encoding]::GetEncoding("Shift_JIS").GetBytes($p_value).Length -
$p_length;
$str_result = $(fnc_Core_StringB $p_value $int_start_location $p_length);
}
catch [Exception]
{
throw $Error[0];
}
return $str_result;
}

#--------------------------------------
#バイト単位で左側から文字列を切り出し
#(パラメータ説明)
#p_value : 文字列
#p_length: 長さ
#--------------------------------------
function global:LeftB(
[string]$p_value = "",
[Int32]$p_length = 0
)
{
#----------------------------
#変数定義
#----------------------------
#結果格納エリア
[string]$str_result = "";
#----------------------------
#処理
#----------------------------
try
{
$str_result = $(fnc_Core_StringB $p_value 0 $p_length);
}
catch [Exception]
{
throw $Error[0];
}
return $str_result;
}

#--------------------------------------
#文字列をバイト単位で切り出すコア関数
#(パラメータの説明)
#p_value : 文字列
#p_start : 開始位置(0スタート)
#p_length: 長さ
#--------------------------------------
function global:fnc_Core_StringB(
[string]$p_value = "",
[Int32]$p_start = 0,
[Int32]$p_length = 0
)
{
#----------------------------
#変数定義
#----------------------------
#List<string>は使えないのでArrayListにしています。
#文字列バイナリの切り出しエリア
[Collections.ArrayList]$bytList_substring = $(New-Object Collections.ArrayList);
#結果格納エリア
[string]$str_result = "";
#文字列のバイナリ変換用配列
[byte[]]$bytArr_value = $null;
#バイナリ変換された文字列と1:1で対応するステータス配列
[byte[]]$bytArr_status = $null;
#開始位置
[Int32]$int_start_location = 0;
#終了位置
[Int32]$int_end_location = 0;
#インデックス
[Int32]$i = 0;
#Shift_JISのエンコーダー
[Text.Encoding]$enc_sjis = [Text.Encoding]::GetEncoding("Shift_JIS");

#----------------------------
#処理
#----------------------------
try
{
#文字列をバイナリの配列に変換
$bytArr_value = $enc_sjis.GetBytes($p_value);
#文字列のバイナリ配列と1:1で対応したShift_JISステータス配列を作成
#0x01=全角の1バイト目、0x02=全角の2バイト目、その他=0x00と設定されます。
$bytArr_status = $(fnc_Create_SJIS_StatusArray $bytArr_value);

#開始位置設定
if($p_start -lt 0)
{ #スタート位置がマイナスの時は0に補正
$int_start_location = 0;
}
else
{
$int_start_location = $p_start;
}

#終了位置設定
$int_end_location = $int_start_location + $p_length - 1;
if($int_end_location -gt $bytArr_value.Length - 1)
{ #終了位置が全体の最終位置を突破していたら、最終位置に補正
$int_end_location = $bytArr_value.Length - 1;
}
#切り出し先頭部分の調整
if($int_start_location -gt 0)
{ #切り出し先頭が0より大きい場合検査開始。
#この16進数値はfnc_Create_SJIS_StatusArray()で設定済
if($bytArr_status[$int_start_location] -eq 0x02)
{ #切り出し先頭が、全角を断ち割った2バイト目にかかっていた場合は補正
#半角スペース(16進数)で補正
$bytArr_value[$int_start_location] = 0x20;
}
}
#切り出し末尾部分の調整
if($int_end_location -lt $bytArr_value.Length-1)
{ #末尾が全体の最終位置でなかった場合検査開始。
#この16進数値はfnc_Create_SJIS_StatusArray()で設定済
if($bytArr_status[$int_end_location] -eq 0x01)
{ #切り出し末尾が、全角を断ち割った1バイト目にかかっていた場合は補正
#半角スペース(16進数)で補正
$bytArr_value[$int_end_location] = 0x20;
}
}
#切り出し末尾部分の調整
#@@@@@@@@@@[LOOP-START]@@@@@@@@@@
for($i = $int_start_location; $i -lt $int_end_location + 1; $i++)
{
#文字列のバイナリを再構成
#[Void]を付けないと配列内容が表示されてしまうので注意
[Void]$bytList_substring.Add($bytArr_value[$i]);
}
#@@@@@@@@@@[LOOP-END ]@@@@@@@@@@
#バイナリから文字列に変換
$str_result = $enc_sjis.GetString($bytList_substring.ToArray());

}
catch [Exception]
{
throw $Error[0];
}
return $str_result;
}

#--------------------------------------
#Shift_JIS文字列のバイナリ配列と1:1対応のステータス配列作成
#全角文字の1バイト目=0x01、2バイト目=0x02、その他=0x00で設定
#(パラメータの説明)
# $bytArr_value : ステータス配列を作りたいバイナリ配列
#--------------------------------------
function global:fnc_Create_SJIS_StatusArray(
[byte[]]$bytArr_value = $null
)
{
#----------------------------
#変数定義
#----------------------------
#ステータス配列
[byte[]]$bytArr_result = $(New-Object byte[] $bytArr_value.Length);
#インデックス
[Int32]$i = 0;
#Shift_JIS全角文字出現フラグ
[Int32]$int_zenkaku_flg = 0;

#----------------------------
#処理
#----------------------------
try
{
#@@@@@@@@@@[LOOP-START]@@@@@@@@@@
for($i = 0; $i -lt $bytArr_value.Length; $i++)
{
if($int_zenkaku_flg -eq 1)
{ #前回、全角の1バイト目が出現したので今回は必ず2バイト目
$bytArr_result[$i] = 0x02;
#全角出現フラグのクリア
$int_zenkaku_flg = 0;
}
elseif(
(($bytArr_value[$i] -ge 0x81) -and ($bytArr_value[$i] -le 0x9F)) -or
(($bytArr_value[$i] -ge 0xE0) -and ($bytArr_value[$i] -le 0xFF))
)
{ #全角文字の1バイト目が出現した
$bytArr_result[$i] = 0x01;
$int_zenkaku_flg = 1;
}
else
{ #全角でない場合
$bytArr_result[$i] = 0x00;
}
}
#@@@@@@@@@@[LOOP-END ]@@@@@@@@@@
}
catch [Exception]
{
throw $Error[0];
}
return $bytArr_result;
}