2019年3月5日火曜日

文字列の扱いではまるポイント

Powershellが期待通りに動かずハマるポイント…
変数の型宣言が無くて楽だけど、いざというときにハマるポイントです。
Powershellに限らず、変数の型宣言が無い言語はみんな同じだと思う。
先ずは…文字、文字列、文字列配列の違いが分かってることが前提です。

# 文字列配列で間違いやすいポイント

# どれが文字列配列か
# ★実際に実行してみてね
## 変数
$String1 = 'これは文字列です。'         # 1つの文字列
$String2 = ('これも文字列です。')       # ()は先に処理するだけ。ここだと無いのと同じ。
$String3 = @('これは文字列配列です')    # @()は配列お宣言。中身が文字列なので、文字列配列
$String4 = @()                      # @()は配列お宣言。配列で初期化
$String4 += 'これも文字列配列です'      # 文字列を配列に追加
$String5 = 'じゃあ…これは?','これは文字列配列です。' # 2つの文字列なので、文字列配列になる。
## 配列長
$String1.Length
$String2.Length
$String3.Length
$String4.Length
$String5.Length
## 1つ目の配列の中身…
$String1[0]     # 文字列(文字配列)なので、1文字目を表示
$String2[0]     # 文字列(文字配列)なので、1文字目を表示
$String3[0]     # 文字列配列なので、1行目を表示
$String4[0]     # 文字列配列なので、1行目を表示
$String5[0]     # 文字列配列なので、1行目を表示

## 演算子の結果だと…
$String1 -match '文字'
$String5 -match '文字'
$String1 -match '?'
$String5 -match '?'
$String1 -match 'これ'
$String5 -match 'これ'
$String1 -ne 'a'
$String5 -ne 'a'

### ↑この結果の違いが理解できないと、バグを埋め込むことになります。
### 文字列として扱いたいのか、文字列配列として扱いたいのかを意識して扱うこと。

@($String1) -match '文字'   # 文字列配列化
$String5[0] -match '文字'   # 文字列配列の1つ目の文字列
$String5[1] -match '文字'   # 文字列配列の2つ目の文字列

2019年3月2日土曜日

文字列配列の抽出

Powershellでの文字列抽出の例
正規表現(match)が多機能で一番汎用性があります。
抽出条件がシンプルなのであれば、処理が軽いワイルドカード(like)が使えます。
ただ、数十万回とか回さないと差は分からないと思う…。
同じスクリプト内で、正規表現とワイルドカードを織り交ぜるとバグを埋め込みやすいので、基本は正規表現に統一した方が良いです。

# 文字列配列
$Array = @()
$Array += '1行目:a'
$Array += '2行目:bb'
$Array += '3行目:ccc'
## 要素数(行数)
$Array.Length
$Array
## 正規表現で抽出①
$Array -match 'b'
## 正規表現で抽出②(上①と同じ結果)
foreach ( $String in $Array ) {
    if ( $String -match 'b' ) {
        $String
    }
}
## ワイルドカードで抽出①
$Array -like '*c'
## ワイルドカードで抽出②(上①と同じ結果)
foreach ( $i in 0..($Array.Length -1) ) {
    if ( $Array[$i] -like '*c' ) {
        $Array[$i]
    }
}

# 要注意
## 演算子 -match や -like は左側が変数か配列化で結果が異なります。この違いを理解していないとハマります。(上の抽出①と②に関連)
$Example1 = '単なる文字列'
$Example2 = @( '1行だけの文字列配列' )
$Example1 -match '文字列'
$Example2 -match '文字列'


カスタムオブジェクト配列の作成と表示

カスタムオブジェクト配列の作成例
Powershellでのカスタムオブジェクトの作成例です。
ネットワーク機器のコンフィグファイルを読み込んで一覧化したりするのに超便利です。
オブジェクトからCSV化、CSVからオブジェクト化(全部文字列になるが…)できて便利です。

# オブジェクト配列化①
# 表の元ネタ
$InterfaceList = @( 'Port1','Port2','Port3' )   # 文字列配列
$IPAddressList = @( '192.168.0.1','192.168.255.1','172.16.255.1' )   # 文字列配列
$NetMaskList   = @( 30,24,24 )   # 数値配列
# オブジェクト化
$Array = New-Object System.Collections.ArrayList    # 配列リストの初期化
foreach ( $i in 0..($InterfaceList.Length -1) ) {   # $InterfaceListのループ
    $Object = [PSCustomObject] @{
        No = $i +1
        Interface = $InterfaceList[$i]
        IPAddress = $IPAddressList[$i]
        NetMask   = $NetMaskList[$i]
    }                       # オブジェクト化
    $Array.Add( $Object )   # オブジェクトの配列追加
}
$List = $Array.ToArray()    # 普通のオブジェクト配列に
$List.Length                # 配列長
$List                       # オブジェクト配列の表示
# CSV化
$CSV = $List | ConvertTo-Csv
$CSV.Length
$CSV


# オブジェクト配列化②
# 表の元ネタ
$InterfaceList = @( 'Port1','Port2','Port3' )   # 文字列配列
$IPAddressList = @( '192.168.0.1','192.168.255.1','172.16.255.1' )   # 文字列配列
$NetMaskList   = @( 30,24,24 )   # 数値配列
# オブジェクト化
$List = @()    # 配列リストの初期化
foreach ( $i in 0..($InterfaceList.Length -1) ) {   # $InterfaceListのループ
    $List += [PSCustomObject] @{
        No = $i +1
        Interface = $InterfaceList[$i]
        IPAddress = $IPAddressList[$i]
        NetMask   = $NetMaskList[$i]
    }                       # オブジェクト化
}
$List.Length                # 配列長
$List  
# CSV化
$CSV = $List | ConvertTo-Csv
$CSV.Length
$CSV


# オブジェクト配列化②改
# 表の元ネタ
$InterfaceList = @( 'Port1','Port2','Port3' )   # 文字列配列
$IPAddressList = @( '192.168.0.1','192.168.255.1','172.16.255.1' )   # 文字列配列
$NetMaskList   = @( 30,24,24 )   # 数値配列
# オブジェクト化
$List = @()    # 配列リストの初期化
$List += foreach ( $i in 0..($InterfaceList.Length -1) ) {   # $InterfaceListのループ
    [PSCustomObject] @{
        No = $i +1
        Interface = $InterfaceList[$i]
        IPAddress = $IPAddressList[$i]
        NetMask   = $NetMaskList[$i]
    }                       # オブジェクト化
}
$List.Length                # 配列長
$List  
# CSV化
$CSV = $List | ConvertTo-Csv
$CSV.Length
$CSV



#CSVデータからのオブジェクト配列化
## 元CSVデータの作成
$CSVData = @()
$CSVData += 'No,Interface,IPAddress,NetMask'
$CSVData += '1,Port1,192.168.0.1,30'
$CSVData += '2,Port2,192.168.255.1,24'
$CSVData += '3,Port3,172.16.255.1,24'
## CSVデータのオブジェクト配列化
$List = $CSVData | ConvertFrom-Csv
$List.Length
$List


文字列配列の作成と表示

Powershellでの文字列配列の処理例
 文字列配列の扱いはシンプル。基本は②で作成し、慣れてきたら③④を使い分けると良い。②も悪くは無いが、配列が1000行以上になると指数関数的に処理が遅くなるので③④に置き換える必要が出てきます。(①はソースが見にくくなるので推奨しない…)

 速度の問題から「System.Text.StringBuilder」を使うような例をよく見るが、文字列配列だけで無く、文字列の組み立てにまで対応していて、Powershellのシンプルな文字列配列の考え方とズレているので使いにくい。(改行を意識する必要がある…)

 どれか1つしか使わない縛りをかけるなら、汎用性が一番高い④しかない。

## 利点: 1行で記述できる、定数なら良い
## 欠点: 追加が出来ない、見づらい(要素数が分かりにくい)
$Array = @('1行目:a','2行目:bb','3行目:ccc')
## 要素数(行数)
$Array.Length
## 表示(全て同じ内容)
$Array
$Array[0..2]
$Array[0..($Array.Length -1)]


# 文字列配列②
## 利点: 見やすい、追加が容易
## 欠点: 加算代入の行数が多くなると、目に見えて遅くなる(多くても100行程度までに)
$Array = @()
$Array += '1行目:a'
$Array += '2行目:bb'
$Array += '3行目:ccc'
## 要素数(行数)
$Array.Length
## 表示(全て同じ内容)
$Array
$Array[0..2]
$Array[0..($Array.Length -1)]


# 文字列配列③
## 利点: 追加が容易、型チェックができる
## 欠点: 速い。遅くならない。
$Array = New-Object System.Collections.Generic.List[string]
$Array.Add( '1行目:a' )
$Array.Add( '2行目:bb' )
$Array.Add( '3行目:ccc' )
## 要素数(行数)
$Array.ToArray().Length
## 表示(全て同じ内容)
$Array
$Array[0..2]
$Array.ToArray()
$Array[0..($Array.ToArray().Length -1)]
## 型を文字列配列に変える
$StringArray = $Array.ToArray()
$StringArray.Length
$StringArray

# 文字列配列④
## 利点: 追加が容易、汎用性が高い
## 欠点: 速い。遅くならない。
$Array = New-Object System.Collections.ArrayList
## 表示
$Array.Add( '1行目:a' )
$Array.AddRange( @('2行目:bb','3行目:ccc') )
## 要素数(行数)
$Array.ToArray().Length
## 表示(全て同じ内容)
$Array
$Array[0..2]
$Array.ToArray()
$Array[0..($Array.ToArray().Length -1)]
## 型を文字列配列に変える
$StringArray = $Array.ToArray()
$StringArray.Length
$StringArray

2018年10月25日木曜日

コマンドレットを作る

1.モジュールの格納先フォルダ

モジュールパスの環境変数を確認し、Modulesフォルダを作成します。

コマンド:
$Env:PSModulePath -Split ';'

実行結果 例:
C:\Users\…\…\ドキュメント\PowerShell\Modules
C:\Program Files\PowerShell\Modules
c:\program files\powershell\6.0.4\Modules

 初期設定では3行のパスが表示さると思います。「C:\Users」から始まるパスがユーザー用のパスです。ここにモジュールを格納するのが無難です。
完全自己管理のパソコンで管理者権限があるのなら、2行目のパスでもいいです。


2.マニフェストファイルの作成

無くても自作コマンドレットは使えますが、インポートしたコマンドレットの新旧の判別が出来た方が便利です。
そのため、マニフェストファイルを作るのがおススメです。

コマンド 例:
New-ModuleManifest -Path New.psd1
Get-ChildItem

実行結果 例:
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2018/10/25      0:20           7644 New.psd1