2019-08-17に更新

手抜きをするプログラマーになるための Power shell の使い方☆m9(^~^)

読了目安:28分

前回の記事

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 できるプログラマーの話しには飽きただろ☆ 手抜きをしようぜ☆?」

KIFUWARABE_80x100x8_01_Futu.gif
「 絶対 客先に連れていってはいけないやつだよな☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 時給2000円分以上の仕事はしないことに 魂が燃えているわよね」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 自社と どの会社が競合してるか 分からんよな☆
ホワイトリストから外れ いろいろなソフトがインストールできない環境で
Power shell は Windows に最初から入っている☆ これを使っていこう☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 営業部門ではないので 手抜きをしながら 要望だけ満たして あとはコーヒーでも飲んでいればいいわけだが、
Windows と同じ Microsoft だから安全とかいう根拠のない理由で Visual studio code をインストールしたわたしは
レジスタをいじって .ps1 をダブルクリックして 起動できるようにした☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 もう .bat は レガシー趣味だぜ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 C# も Java も PHP も Rust も Python3 も JavaScript も書けるお父んが 行きついた答えは Power shell かだぜ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 手抜きの黄金パターンを お教えしよう☆」

practice-2
    |
    +-- main-2.ps1
    |
    +-- modules
        |
        +-- module-2.ps1

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 とりあえず modules というフォルダーを作って サブルーチンは全部 そこへ投げ入れろだぜ☆
『ファイル数が多すぎる』 とか、 『フォルダー数が多すぎる』 とかいった小言は封殺できる☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 『mainなんとかをダブルクリックするだけですよ』 という言葉は
休日に起こされてエラー対応するエンジニアに安心感を与える☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 人間力学をハックするのは止めなさい!」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 本当は Privilege へ昇格させたり、コマンドライン・パラメーターを手抜きする方法などいろいろあって もう少し手間をかけるが
今は省く☆」

Power shell でのコールバックの作り方

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 コールバックを使えると 手抜きの幅が 一気に広がる☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 嫌なプレゼンテーションだぜ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 Visual studio code を出せだぜ☆ Power shell を使って コールバック を書く方法を教えてやる☆
たとえ話を聞くより早いだろう☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 10個ぐらいの短いサンプル・プログラムを書くと 頭に叩き込めると思う☆ おそらく最速だぜ☆」

main-ep1.ps1

# +
# | Episode 1.
# +

# +
# | Hash map.
# | In another language, it is also called a dictionary.
# +
$dict = @{
    Food  = "palm";
    Plice = 20;
}

# - Function definition.
function Invoke-Main($dict) {
    Write-Host "I bought $($dict.Food) for $($dict.Plice) yen."
    $dict.Plice += 50
}

# - Function call.
Invoke-Main $dict

# - End.
Write-Host "Info           | $($dict.Plice) yen."
Write-Host "Info           | Finished."

Output

I bought palm for 20 yen.
Info           | 70 yen. 
Info           | Finished.

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 めんどくさいんで 適当に書いたが、
連想配列、関数定義、関数呼出し、参照渡し の説明は終わった☆
何それと思ったら勝手に調べろだぜ☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 手抜きね……」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 どんどん見ていこう☆」

main-ep2.ps1

# +
# | Episode 2.
# +

# +
# | Hash map.
# | In another language, it is also called a dictionary.
# +
$dict = @{
    Food       = "palm";
    Plice      = 20;

    # - You can also include functions.
    OnStarted  = {
        Write-Host "Info           | Started."
    };
    OnFinished = {
        Write-Host "Info           | Finished."
    };
}

# - Function definition.
function Invoke-Main($dict) {
    $dict.OnStarted.Invoke()
    Write-Host "I bought $($dict.Food) for $($dict.Plice) yen."
    $dict.Plice += 50
    $dict.OnFinished.Invoke()
}

# - Function call.
Invoke-Main $dict

# - End.
Write-Host "Info           | $($dict.Plice) yen."
Write-Host "Info           | Finished."

Output

Info           | Started.
I bought palm for 20 yen.
Info           | Finished.
Info           | 70 yen.
Info           | Finished.

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 連想配列には スクリプトをぶら下げることもできる☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 もうコールバック関数来た☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 コールバック関数は何が便利なの?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 処理 を1本にまとめたいときに使う☆ 使っていれば そのうち分かる☆
どんどん行こう☆」

main-ep3.ps1

# +
# | Episode 3.
# +

# +
# | Hash map.
# | In another language, it is also called a dictionary.
# +
$dict = @{
    Food       = "palm";
    Plice      = 20;

    # - You can also include functions.
    OnStarted  = {
        $dict = $args[0]
        Write-Host "Info           | Started. [$($dict.Message)]"
    };
    OnFinished = {
        Write-Host "Info           | Finished."
    };
}

# - Function definition.
function Invoke-Main($dict) {

    $dict2nd = @{
        Message = "Hello, Old man.";
    };
    $dict.OnStarted.Invoke($dict2nd)

    Write-Host "I bought $($dict.Food) for $($dict.Plice) yen."
    $dict.Plice += 50
    $dict.OnFinished.Invoke()
}

# - Function call.
Invoke-Main $dict

# - End.
Write-Host "Info           | $($dict.Plice) yen."
Write-Host "Info           | Finished."

Output

Info           | Started. [Hello, Old man.]
I bought palm for 20 yen.
Info           | Finished.
Info           | 70 yen.
Info           | Finished.

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 コールバック関数が引数を受け取ることもできる☆
連想配列を1個受け取るのが 手っ取り早いぜ☆ 細かいことやってたら プログラムが分けわからなくなる☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 連想配列の参照を渡しているようでは グローバル変数をマウントしてるのと変わらんけどな☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 そうそう……☆
設計するより グローバル変数をマウント する方が手っ取り早い……☆

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 エラーの影響範囲の切り分け ができないんだけど?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 エラーの影響を切り分けすることを 諦めろ だぜ☆
どこかがミスったら システムは落ちる☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 So bad. わらう☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 一旦、 メイン・ルーチン と サブ・ルーチン を分けてみようぜ☆?」

Episode 4

episode-4
    |
    +-- modules
    |    |
    |    +-- module-1.ps1
    |
    +-- i-have-banana.ps1
    |
    +-- i-have-parm.ps1

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 フォルダー階層は 練習なんで こうしておくかだぜ☆
慣れてくると もっといい方法があるが 一度にやると 覚えられないだろうからな☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 みんな ググって最初に出てくる サンプル・プログラム みたいなコード書くわよね。
それが コンピューター だと思って疑問に思わず メンテナンス してるのよ」

episode-4\modules\module-1.ps1

# +
# | Episode 4.
# +

# - Function definition.
function Invoke-Main($dict) {
    # - You can also include functions.
    $dict.OnStarted = {
        $dict = $args[0]
        Write-Host "Info           | Started. [$($dict.Message)]"
    };
    $dict.OnFinished = {
        Write-Host "Info           | Finished."
    };

    $dict2nd = @{
        Message = "Hello, Old man.";
    };
    $dict.OnStarted.Invoke($dict2nd)

    Write-Host "I bought $($dict.Food) for $($dict.Plice) yen."
    $dict.Plice += 50
    $dict.OnFinished.Invoke()

    # - End.
    Write-Host "Info           | $($dict.Plice) yen."
    Write-Host "Info           | Finished."
}

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 modules フォルダーの下にあるスクリプトは 誰も読まないから 伸び伸び書けるぜ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 何と戦っているのか……☆?」

i-have-banana.ps1

# +
# | Episode 4.
# +

# +---------+
# |  Ready  |
# +---------+
$dict = @{
    Food  = "banana";
    Plice = 160;
}

# +------+
# |  Go  |
# +------+

Write-Host "Exit           | $(Get-Location)"
Set-Location $PSScriptRoot
Write-Host "Enter          | $(Get-Location)"

# Read script. (Dot source)
. ".\modules\module-1.ps1"

Invoke-Main $dict

Output

Exit           | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1
Enter          | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-2\episode-4
Info           | Started. [Hello, Old man.]
I bought banana for 160 yen.
Info           | Finished.
Info           | 210 yen.
Info           | Finished.

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 いわゆる メイン・プログラム には、 Ready セクションと Go セクション だけをおけだぜ☆
まだ 設定ファイル を利用したり改善の余地はあるが、
だいたい これ以上 複雑にすると プログラマーは自分の首を絞めることになるぜ☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 電話を掛けている机の向かいの システム・エンジニアが IPアドレス を質問してきたりするわよね」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 だいたい どんなプログラム書いたっけ と忘れた頃に 客先のIPアドレスが変わったりする☆
リモート・サーバーにログインして 動いているプログラムの設定ファイルを見るのが手っ取り早い☆
ちょっと調べるふりをして 30秒 ぐらいで返事ができると 電話を折り返さず 仕事が スムーズに進むぜ☆」

i-have-palm.ps1

# +
# | Episode 4.
# +

# +---------+
# |  Ready  |
# +---------+
$dict = @{
    Food  = "palm";
    Plice = 20;
}

# +------+
# |  Go  |
# +------+

Write-Host "Exit           | $(Get-Location)"
Set-Location $PSScriptRoot
Write-Host "Enter          | $(Get-Location)"

# Read script. (Dot source)
. ".\modules\module-1.ps1"

Invoke-Main $dict

Output

Exit           | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-2\episode-4
Enter          | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-2\episode-4
Info           | Started. [Hello, Old man.]
I bought palm for 20 yen.
Info           | Finished.
Info           | 70 yen.
Info           | Finished.

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 ここでは パラメーターを変えれる 例だけ紹介しているが、もちろんコールバック関数を利用して 処理も変えれる☆
例えば ProductUAT の2つに分けれる☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 本番用(Prod)と、受入テスト環境用(UAT)で同じプログラムを走らせるが、受入テスト環境用には処理をちょっと足したいというときは
コールバック関数を使って 処理を挟み込めだぜ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 何を言ってるのか分からん☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 ここで ちょっと改造だぜ☆」

    $dict2nd = @{
        Message = "Hello, Old man.";
    };
    $dict.OnStarted.Invoke($dict2nd)

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 練習用に 連想配列を使って コールバック関数の引数として渡していたが……☆」

function Invoke-Main($dict) {
    $dict.Message = "Hello, Elder brother.";
    $dict.OnStarted.Invoke($dict)
}

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 メイン・プログラムの最初に作った連想配列に ぶら下げて 一気通貫 するのもOK☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 どっちの書き方も Rust言語 ではエラーになる☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 自分しか いじらない プログラム という前提よね。
複数の会社で いじるプログラム だと 隣の会社は指示下にないわ、修正できないから メインコード捨てて勝手にやるわ、
定められたフォーマットの仕様上 挟めるメモリ空間が足りないわと
可能を不可能にするプログラム の始まりなのよ」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 わたしのプログラムの書き方は 捨てるプログラム一から書き直すプログラム だぜ☆
秘伝のタレ なんか持ってて いつまでも捨てないプログラムを書いている人には 評判が悪いだろうなんだぜ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 git で差分更新して管理しているくせに全部捨てて 全部新しいファイルになってたりするよな☆」

ハンドルバーのようなもの

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 まだ 手抜きのための便利アイテムを説明しきっていないので、説明していくぜ☆」

Handlebars.js

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 JavaScript には Handlebars という便利なものがあるんだが、
Power shell にはないので 簡単なものを 自作するぜ☆ やることは……☆」

My name is {{userName}}.

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 を☆」

My name is Muzudho.

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 に置換することだけ できるようにしようぜ☆
これだけで 逃げ道は広がる、手抜きができる ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 人生40点 で生き残りを目指す生き方だな☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 Power shell で UTF-8 エンコーディングしたファイルは with BOM になるから 他のスクリプト言語で読めなくて役に立たないのよ」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 UTF-8 without BOM で出力する方法も説明する☆」

config-TEMPLATE.ini

[Profile]

Name = {{userName}}
Age = {{age}}

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 全部変数にするみたいな書き方をしたら あんまり設定ファイルの意味は無いが
練習で 設定ファイルに プレースホルダーを書いておくぜ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 {{ }} みたいなやつか☆」

config.ini

[Profile]

Name = Muzudho
Age = 39

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 こんな感じに変換してくれれば便利だぜ☆
例えば 設定ファイル と ドキュメント の両方を テンプレート にしておけば 齟齬がなくなる☆」

config-STEREOTYPE.ini

userName = Muzudho
age = 39

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 タネ になる方も 設定ファイルにしておけだぜ☆
手書きするのではなく、プログラムで動的に出力することになると思う☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 ファイルを書きだすプログラムも 示そう☆」

main.ps1

# +
# | Episode 5.
# +

# +---------+
# |  Ready  |
# +---------+
$dict = @{
    # Absolute path.
    Locations = @{
        StereotypeDir = "$($PSScriptRoot)\@auto-gen"
    };
}

# +------+
# |  Go  |
# +------+

Write-Host "Exit           | $(Get-Location)"
Set-Location $PSScriptRoot
Write-Host "Enter          | $(Get-Location)"

# Read script. (Dot source)
. ".\modules\module-1.ps1"

New-Stereotype $dict

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 まず メイン・プログラム を手癖で書こうぜ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 パターン入ってる☆

modules\module-1.ps1

function New-Stereotype($dict) {
    $path = $dict.Locations.StereotypeDir
    $file = "$($path)\config-STEREOTYPE.ini"
    $text = @"
userName = Muzudho
age = 39
"@
    $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False

    # - Create a directory if it not exists.
    if ( !(Test-Path $path) ) {
        Write-Host "Create         | [$($path)] directory."
        New-Item -ItemType directory -Path $path
    }

    # - Write a file.
    Write-Host "Write          | [$($file)]."
    [System.IO.File]::WriteAllLines($file, $text, $Utf8NoBomEncoding)
}

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 これでファイルも 書き出してくれるぜ☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 @auto-gen ってフォルダーは何なの?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 頭に @ が付いているファイルは リリースしない、本番環境に入れない、
ぐらいの意味で 勝手にルールを作って 使っている☆
@auto-genauto-generated の略☆」

iniファイルを読もうぜ☆?

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 前にも書いたが、前の記事を探すのがめんどくさいんで再掲するぜ☆」

modules\ini-parser.ps1

# +
# | See also:
# |     INI file parsing in PowerShell
# |     https://stackoverflow.com/questions/417798/ini-file-parsing-in-powershell
# +
Function Read-IniFile ($file) {
    $ini = @{ }

    # Create a default section if none exist in the file. Like a java prop file.
    $section = "NO_SECTION"
    $ini[$section] = @{ }

    switch -regex -file $file {
        "^\[(.+)\]$" {
            $section = $matches[1].Trim()
            $ini[$section] = @{ }
        }
        "^\s*([^#].+?)\s*=\s*(.*)" {
            $name, $value = $matches[1..2]
            # skip comments that start with semicolon:
            if (!($name.StartsWith(";"))) {
                $ini[$section][$name] = $value.Trim()
            }
        }
    }
    $ini
}

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 .ini ファイルを利用する方法を示すぜ☆」

main-2.ps1

# +
# | Episode 5.
# +

# +---------+
# |  Ready  |
# +---------+
$dict = @{
    # Absolute path.
    Locations = @{
        StereotypeDir = "$($PSScriptRoot)\@auto-gen"
    };
}

# +------+
# |  Go  |
# +------+

Write-Host "Exit           | $(Get-Location)"
Set-Location $PSScriptRoot
Write-Host "Enter          | $(Get-Location)"

# Read script. (Dot source)
. ".\modules\ini-parser.ps1"

$cnf = Read-IniFile "$($dict.Locations.StereotypeDir)\config-STEREOTYPE.ini"

$cnf.GetEnumerator() | ForEach-Object {
    $section = $_;
    Write-Host "Section        | [$($section.Name)]";

    $section.Value.GetEnumerator() | ForEach-Object {
        $item = $_;
        Write-Host "Item           | [$($item.Name)] = [$($item.Value)]";
    }
}

Output

Exit           | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-3
Enter          | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-3
Section        | [NO_SECTION]
Item           | [age] = [39]
Item           | [userName] = [Muzudho]

KIFUWARABE_80x100x8_01_Futu.gif
「 こんなけ掛ければ なんなりとできそうだな☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 業務に関係のあるページですか? とか出てきて会社のPCから見れないんだけど?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 転職しろ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 .ini ファイルを 連想配列ではなく ただのテキストとして読み込む方法を紹介する☆」

main-3.ps1

# +
# | Episode 5.
# +

# +---------+
# |  Ready  |
# +---------+
$dict = @{
    # Absolute path.
    Locations = @{
        ConfigDir = "$($PSScriptRoot)";
    };
}

# +------+
# |  Go  |
# +------+

Write-Host "Exit           | $(Get-Location)"
Set-Location $PSScriptRoot
Write-Host "Enter          | $(Get-Location)"

$file = "$($dict.Locations.ConfigDir)\config-TEMPLATE.ini"
$text = [System.IO.File]::ReadAllLines($file) -join "`r`n"

Write-Host "Conf           | v";
Write-Host $text
Write-Host "Conf           | ^";

Output

Exit           | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-3
Enter          | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-3
Conf           | v
[Profile]

Name = {{userName}}
Age = {{age}}
Conf           | ^

KIFUWARABE_80x100x8_01_Futu.gif
「 あとは 文字列置換して 保存すれば良さそうだな☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 とりあえず、開発が進むごとに 処理が遅くなるBADなアルゴリズムで書いてみるぜ☆」

main-4.ps1

# +
# | Episode 5.
# +

# +---------+
# |  Ready  |
# +---------+
$dict = @{
    # Absolute path.
    Locations = @{
        ConfigDir     = "$($PSScriptRoot)";
        StereotypeDir = "$($PSScriptRoot)\@auto-gen";
    };
}

# +------+
# |  Go  |
# +------+

Write-Host "Exit           | $(Get-Location)"
Set-Location $PSScriptRoot
Write-Host "Enter          | $(Get-Location)"

# Read script. (Dot source)
. ".\modules\ini-parser.ps1"

$ster = Read-IniFile "$($dict.Locations.StereotypeDir)\config-STEREOTYPE.ini"

$file = "$($dict.Locations.ConfigDir)\config-TEMPLATE.ini"
$text = [System.IO.File]::ReadAllLines($file) -join "`r`n"

# Replace. But, *BAD* algorithm.
$ster.GetEnumerator() | ForEach-Object {
    $section = $_;
    $section.Value.GetEnumerator() | ForEach-Object {
        $item = $_;
        $text = $text.Replace( "{{$($item.Name)}}", $item.Value)
    }
}

Write-Host "Conf           | v";
Write-Host $text
Write-Host "Conf           | ^";

Output

Exit           | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-3
Enter          | C:\Users\むずでょ\OneDrive\ドキュメント\practice-ps1\practice-3
Conf           | v
[Profile]

Name = Muzudho
Age = 39
Conf           | ^

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 リクツが分かれば 高速化しろだぜ☆ ファイルも書き出せだぜ☆」

おまとめ

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 とりあえず 全部のコードを再掲する☆」

config-TEMPLATE.ini

[Profile]

Name = {{userName}}
Age = {{age}}

main.ps1

# +
# | Episode 5.
# +

# +---------+
# |  Ready  |
# +---------+
$dict = @{
    # Absolute path.
    Locations = @{
        ScriptDir  = "$($PSScriptRoot)";
        AutoGenDir = "$($PSScriptRoot)\@auto-gen";
    };
}

# +------+
# |  Go  |
# +------+

Write-Host "Exit           | $(Get-Location)"
Set-Location $PSScriptRoot
Write-Host "Enter          | $(Get-Location)"

# Read script. (Dot source)
. ".\modules\ini-parser.ps1"
. ".\modules\module-1.ps1"

# Out-Null waits for the process to finish.
New-Stereotype $dict | Out-Null
$stereotype = Read-IniFile "$($dict.Locations.AutoGenDir)\config-STEREOTYPE.ini"

$text = Read-File "$($dict.Locations.ScriptDir)\config-TEMPLATE.ini"
$text = Edit-PlaceHolder $text $stereotype

Write-File "$($dict.Locations.AutoGenDir)\config.ini" $text | Out-Null

# - End.
Write-Host "Info           | Finished."

modules\ini-parser.ps1

# +
# | See also:
# |     INI file parsing in PowerShell
# |     https://stackoverflow.com/questions/417798/ini-file-parsing-in-powershell
# +
Function Read-IniFile ($file) {
    Write-Host "Read           | [$($file)]."
    $ini = @{ }

    # Create a default section if none exist in the file. Like a java prop file.
    $section = "NO_SECTION"
    $ini[$section] = @{ }

    switch -regex -file $file {
        "^\[(.+)\]$" {
            $section = $matches[1].Trim()
            $ini[$section] = @{ }
        }
        "^\s*([^#].+?)\s*=\s*(.*)" {
            $name, $value = $matches[1..2]
            # skip comments that start with semicolon:
            if (!($name.StartsWith(";"))) {
                $ini[$section][$name] = $value.Trim()
            }
        }
    }
    $ini
}

modules\module-1.ps1

function New-Stereotype($dict) {
    $path = $dict.Locations.AutoGenDir
    $file = "$($path)\config-STEREOTYPE.ini"
    $text = @"
userName = Muzudho
age = $(10+29)
"@

    # - Create a directory if it not exists.
    if ( !(Test-Path $path) ) {
        Write-Host "Create         | [$($path)] directory."
        New-Item -ItemType directory -Path $path
    }

    # - Write a file.
    Write-Host "Write          | [$($file)]."
    [System.IO.File]::WriteAllLines($file, $text)
}

function Read-File($file) {
    Write-Host "Read           | [$($file)]."
    [System.IO.File]::ReadAllLines($file) -join "`r`n"
}

function Write-File($file, $text) {
    $path = Split-Path $file -parent 

    # - Create a directory if it not exists.
    if ( !(Test-Path $path) ) {
        Write-Host "Create         | [$($path)] directory."
        New-Item -ItemType directory -Path $path
    }

    # - Write a file.
    Write-Host "Write          | [$($file)]."
    [System.IO.File]::WriteAllLines($file, $text)
}

function Edit-PlaceHolder($text, $stereotype) {
    # Replace. But, *BAD* algorithm.
    $stereotype.GetEnumerator() | ForEach-Object {
        $section = $_;
        $section.Value.GetEnumerator() | ForEach-Object {
            $item = $_;
            $text = $text.Replace( "{{$($item.Name)}}", $item.Value)
        }
    }
    $text
}

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 疲れた……☆」

手抜きをする ディレクトリ構成の例

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 サンプル・プログラムなんかだと……☆」

client-1
    +
    |
    +-- config.ini
    |
    +-- README.md
    |
    +-- source.ps1

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 設定ファイルがあって 実行ファイルがある、ぐらいの構成だろ☆
よくて リードミーが付くぐらい☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 作っているときは 手抜きができる が、
開発が進むと 手抜きができなくなる
もっと運用が進んで追加の要求がきた時を見据えて 大きな手抜きをしたい とは思わないかだぜ☆?」

KIFUWARABE_80x100x8_01_Futu.gif
「 子どもに覚えさせたくない格言だぜ☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 これが一番シンプルなんじゃないの?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 モジュールの追加 と サブシステム化、 プラットフォーム変更によるデプロイ が今後起こる☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 例えば Windows 10 とか 一生使い続けれると思っているかも知れないが、
10年後に Windows 30 とかになるか分からないぜ☆?」

KIFUWARABE_80x100x8_01_Futu.gif
「 サポート期間の延長ぐらいあるだろ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 Windows 40 が出たときまで そんなことが言ってられるかだぜ☆?」

サブ・システム化

Case 1 : BAD

client-1
    +
    |
    +-- config.ini
    |
    +-- README.md
    |
    +-- source.ps1
        # Command line parameter `--mode 1` or `--mode 2`

Case 2: BAD

client-1
    +
    |
    +-- config.ini
    |
    +-- README.md
    |
    +-- source.ps1
    |
    +-- source-2.ps1

Case 3: BAD

C:\
    |
    +-- client-1
    |   |
    |   +-- config.ini
    |   |
    |   +-- README.md
    |   |
    |   +-- source.ps1
    |
    +-- client-1-2
        |
        +-- config.ini
        |
        +-- README.md
        |
        +-- source.ps1

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 アプリケーションを追加すると こうなる☆
他にも データベースを使うなり やりようはあるかもしれないが、
現場で何が起こるか 見えていないのは
プログラマーはあんまりチーム作業しない個人技 なところからくるのだろう☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 何が起こるの?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 お客は 最初 2社とか 3社かも知れないが、
そのうち 10社 20社 と増えていき、さらに 会社ごとに 前株なのか後株なのか、
データベースの表示列名を短縮したり、短縮してはいけなかったり、
メッセージは外出しして 対応できるフォーマット にしておいたり、
UTF-8 でいいのか Shift-JIS にこだわるのかも 分かれる☆
設定ファイルは .ini を メモ帳(Windows Notepad) で触りたい お客さんもいる☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 あと見えていないが そもそも ログ・ファイル の出力先フォルダーと、
受け取ったデータを蓄積していく 月別アーカイブ フォルダーが どこかにある☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 Case 1 ~ 3 でも対応できるんじゃないの?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 Case 3 がダメな理由は簡単だぜ☆
IP アドレスが変わった時に config.ini ファイルが10個あったとき、
担当者にここを直してくださいと ファイル10個分の ファイルパスを並べて 指示を出すのは いかがなものなのか☆?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 自分で直すなら 10個ぐらい と思うかもしれないが それもダメだぜ☆
50個なら どうなのか? 100個なら?」

KIFUWARABE_80x100x8_01_Futu.gif
「 どうすりゃいいんだぜ☆?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 お客さんが 1つ あるなら、
その お客さん1つ に対応した 設定ファイル を1つ を用意しろだぜ☆
余分と思うのは お前の勘違い だぜ☆」

Case 4 : Good

client-1.ini

[Profile]
IPAddress = 127.0.0.1
Suffix = ABC

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 IPアドレスを書いておく 設定ファイル は1か所にしろだぜ☆
お客の短い頭文字も 社内で決めておけだぜ☆ フォルダー名や、インターナルなエラー通知メールの件名に使う☆」

Case 5 : Better

C:\
    |
    +-- client-1
        |
        +-- feature-1
        |   |
        |   +-- README.md
        |   |
        |   +-- source.ps1
        |
        +-- feature-1
        |   |
        |   +-- README.md
        |   |
        |   +-- source.ps1
        |
        +-- etc
            |
            +-- client.ini
            |
            +-- modules
                |
                +-- feature-1.ini
                |
                +-- feature-2.ini

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 設定ファイルは etc フォルダーを作って その中に まとめて入れろだぜ☆
オペレーターが触ることのない設定ファイルは etc\modules の下に隠せだぜ☆」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 なんで etc とかいう 分け分かんないフォルダー名の中に 設定ファイルを入れてしまうの?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 相手に合わせて 3段階 の説明を用意しておくことは プログラマーの作法だぜ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 まず相手が 手練れ の場合☆
Linux で 設定ファイルを etc フォルダーの下に置くことに慣れてるんでフォルダー名を真似した
この説明で 歯向かってこられたら 手抜きは諦めろ だぜ☆
売り上げに貢献しないルーチンを身に付ける精神修養をしろだぜ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 次に相手が サンプル・プログラムぐらいは書ける人 の場合☆
Linuxで ファイルの置き場所は FHS という仕様で決まっている☆ 自分の選ぶ仕様を1つ明示しておけば他の人も真似することができる
この説明で 歯向かってこられたら 手抜きは諦めろ だぜ☆
みなそれぞれ 売り上げに貢献しない そのときの気分で名付けたフォルダー名に悩む趣味でも愛でろだぜ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 次に相手が コンピューターはわたしを上から目線で見下してくるゴミくずだと思っている人 の場合☆
その他フォルダーに入れておきました
この説明で 歯向かってこられたら 手抜きは諦めろ だぜ☆
どう考えても etc より文字数が長くなるフォルダー名を 明日止めてしまうかもしれない担当者の好みで付けて その後もメンテナンスしろだぜ☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 レベルデザインの落差が大きい……☆」

トリニティ・デザイン

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 そんな こんな で 現場で改善していくと ある程度落ち着く 冗長なデザイン というものがある☆
わたしが勝手に トリニティ・デザイン と名付けた☆ 説明しよう☆」

  • Client
    • Feature
      • Operation

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 だいたい この3段階の階層構造 で、いろいろ対応できる☆ よく使うんで省略すると☆」

  • Clie
    • Feat
      • Oper

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 クリーフィートオパー の3つだぜ☆ これは ファイル・パスの付け方 を方向づけしている☆」

# Tri pattern.
Clie\Feat\Oper

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 例えばこれは トリ・パターン☆ 実際使うときは……☆」

# Destination tri pattern.
$dsTriDir = "$($clieDir)\$($featDir)\$($operDir)"

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 みたいな感じにする☆」

KIFUWARABE_80x100x8_01_Futu.gif
「 明日辞めてしまうかもしれない お父んの特殊パターン なんか覚えてどうすんだぜ☆?」

# Duo pattern.
Clie\Feat

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 クリーフィート をつなげたパターンは デュオ・パターン だぜ☆
フィートとオペだけくっつくパターンは少ないので、少ないことに 用語は作らない☆ 研究者ではなく、実務屋だからな☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 クリー は メール担当者が名乗る何社 と1対1対応しろだぜ☆ 契約を取る、無くなる、にフォルダーを作る、消すが対応する☆
フィート は 案件 と1対1対応しろだぜ☆ 人間力学で連絡には『あの件ですが……』とドメイン的な切れ目があるから 気づけ☆
オパー は 休日に働くシステム・エンジニア に出す このファイルをクリックしろ、という指示で切り分けろだぜ☆ 最小単位☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 それを加味して 少しマシにした ディレクトリーのデザインは次の通り☆」

Case 6 : More better

C:\
    |
    +-- client-1
        |
        +-- etc
        |   |
        |   +-- client.ini
        |   |
        |   +-- feature-1
        |       |
        |       +-- operation-1.ini
        |       |
        |       +-- operation-2.ini
        |
        +-- opt
             |
             +-- feature-1
                |
                +-- operation-1
                |   |
                |   +-- README.md
                |   |
                |   +-- source.ps1
                |
                +-- operation-2
                    |
                    +-- README.md
                    |
                    +-- source.ps1

KIFUWARABE_80x100x8_01_Futu.gif
「 うわっ、めんどくせっ ☆!」

OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif
「 opt とかいう分けのわからないディレクトリが1階層 増えたわよ?」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 分けわからないなら 触らないだろ☆ 都合がいい☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 逆に、オペレーターにダブルクリックさせたいファイルもあるだろ☆
それは home ディレクトリーの下に入れておくといい☆ サンプルを示そう☆」

Case 7 : More better

C:\
    |
    +-- client-1
        |
        +-- etc
        |   |
        |   +-- client.ini
        |   |
        |   +-- feature-1
        |       |
        |       +-- operation-1.ini
        |       |
        |       +-- operation-2.ini
        |
        +-- home
        |    |
        |    +-- Muzudho
        |        |
        |        +-- feature-1
        |           |
        |           +-- operation-1
        |           |   |
        |           |   +-- i-have-a-banana.ps1
        |           |
        |           +-- operation-2
        |               |
        |               +-- i-have-a-palm.ps1
        |
        +-- opt
        |    |
        |    +-- feature-1
        |       |
        |       +-- operation-1
        |       |   |
        |       |   +-- application.ps1
        |       |
        |       +-- operation-2
        |           |
        |           +-- application.ps1
        |
        +-- README-feature-1-operation-1.md
        |
        +-- README-feature-1-operation-2.md

KIFUWARABE_80x100x8_01_Futu.gif
「 さらに めんどくせ~っ ☆!」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 めんどくさい前準備を終わらせておくことで、現場で手抜きができる んだぜ☆
これを タネを仕込む と呼ぶ☆」

KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif
「 まだまだ 仕込み はある☆」

<書きかけ>

ツイッターでシェア
みんなに共有、忘れないようにメモ

むずでょ

光速のアカウント凍結されちゃったんで……。ゲームプログラムを独習中なんだぜ☆電王戦IIに出た棋士もコンピューターもみんな好きだぜ☆▲(パソコン将棋)WCSC29一次予選36位、SDT5予選42位▲(パソコン囲碁)AI竜星戦予選16位

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント