📏

[PowerShell]挿入するタブ数を制御しインデントを合わせ配列を一括出力する方法

2023/10/13に公開

概要

複数行(データ件数)、複数列(項目数)ある文字列配列を一括でコンソール出力(Write-Host)しようとPowerShellスクリプトを作成していました。
個々の項目をタブ区切り(`t)で表示しようとすると、左辺の文字数によってインデントがズレてしまいます。

Bing AI(Microsoft Copilot・Bing チャット)も活用して対応する方法を見つけたので合わせて紹介します。

この記事のターゲット

  • PowerShellユーザーの方
  • 複数要素もっている配列をインデントを合わせてコンソールに出力したい方
  • 便利なBing AIの活用方法も知りたい方

問題

PowerShellで配列にある複数の項目をタブ区切り(`t)で表示しようと、最初は下記のようなコマンドを準備しました。

タブ数1つ固定でコンソールに表示するコマンドと結果
PS C:\WINDOWS\system32> $strings = @("1234567890", "123", "1234567890", "123456789012345678901234567890", "1234567890")
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> Write-Host "文字列 [$($strings[0])]`t、バイト数(Shift-JIS)[$([System.Text.Encoding]::GetEncoding("shift_jis").GetByteCount($strings[0]))] byte"
文字列 [1234567890]     、バイト数(Shift-JIS)[10] byte
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> Write-Host "文字列 [$($strings[1])]`t、バイト数(Shift-JIS)[$([System.Text.Encoding]::GetEncoding("shift_jis").GetByteCount($strings[1]))] byte"
文字列 [123]    、バイト数(Shift-JIS)[3] byte
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> Write-Host "文字列 [$($strings[3])]`t、バイト数(Shift-JIS)[$([System.Text.Encoding]::GetEncoding("shift_jis").GetByteCount($strings[3]))] byte"
文字列 [123456789012345678901234567890] 、バイト数(Shift-JIS)[30] byte
PS C:\WINDOWS\system32>

このようなにタブ数を1つで固定してしまうと、左辺にある文字列の文字数の長さにより、
以降にある文字列のインデントが崩れ見づらい表示となります。

わかりやすくするためコマンド結果のみを抜粋してみます。

上記のコマンド結果のみを抜粋
文字列 [1234567890]     、バイト数(Shift-JIS)[10] byte
文字列 [123]    、バイト数(Shift-JIS)[3] byte
文字列 [123456789012345678901234567890] 、バイト数(Shift-JIS)[30] byte

このように左辺の後に続く"、"以降の文字列のインデントがズレていて結果が見にくいです。

Bing チャットでコマンドを作成してみた

人工知能(AI)チャットボット(対話型人工知能)で代表的なサービスは、OpenAIが開発したChatGPTが上げられます。
今回はMicrosoftが開発したAIチャットボット、Bing AI(表示されているサービス名だと“Bing チャット”)を使ってコマンド(ソースコード)を作成してもらいました。

ちなみに私がBing チャットを使用する際はBing.com 経由のチャットボット機能ではなく、Microsoft Edgeに標準搭載されたMicrosoft Copilot with Bing Chatを使用しています。
Webブラウザで、すぐチャットボットにアクセスできるのは大きなメリットです。

また、Bingのチャットボットサービスでは、回答のスタイルを選択可能で下記3種類があります。

  • より創造的に
  • よりバランスよく
  • より厳密に

Microsoft EdgeのサイドバーにあるBing チャット機能
画像:Microsoft EdgeのBing チャット機能(右上が呼び出しボタン)

今回、私は「 より創造的に 」を選択し使用しました。用途によりスタイルを変更する事で回答の精度に変化が見られます。
個人的に独創性が高くなりやすいスタイル“より創造的に”が好みで、よく使ってます。

参考情報:Bing チャット のスタイルについて

https://note.com/it_navi/n/n7cf8f5a7dce4

Bing AI(チャット)でのやりとり

❓ Bing AIへの依頼内容("より創造的に")

PowerShell で複数の要素がある文字列の配列をコンソール出力(Write-Host)する際に、配列内にある最大の文字数により挿入するタブ("`t")の数を制御しインデントを揃えて出力する事ができるプログラムを作成して

あまり複雑な条件を入れるとプログラムを複雑になってしまう為、上記の通り、なるべくシンプルな文章で送信しました。

✅ Bing AIからの回答

こんにちは、これは Bing です。PowerShell のプログラムについてお手伝いできると思います。😊

あなたが求めているプログラムは、配列の各要素の文字数を調べて、最大の文字数に合わせてタブを挿入するというものですね。その場合、以下のようなコードを試してみてください。

# 配列を定義する
$array = @("あいうえお", "かきくけこさしすせそ", "たちつてと", "なにぬねのはひふへほ")

# 配列の各要素の文字数を取得する
$lengths = $array | ForEach-Object { $_.Length }

# 配列の中で最大の文字数を求める
$maxLength = ($lengths | Measure-Object -Maximum).Maximum

# 配列の各要素をコンソール出力する
foreach ($element in $array) {
    # タブを挿入する回数を計算する
    $tabs = [Math]::Ceiling(($maxLength - $element.Length) / 4)

    # タブを作成する
    $tabString = "`t" * $tabs

    # 要素とタブを結合して出力する
    Write-Host ($element + $tabString)
}

このコードは、配列の各要素の文字数を取得し、その中で最大の文字数を求めます。そして、配列の各要素を出力する際に、最大の文字数から要素の文字数を引いてタブを挿入する回数を計算します。タブは 4 文字分として計算し、切り上げします。タブを作成したら、要素と結合して出力します。

このコードを実行すると、以下のような結果が得られます。

あいうえお`t`t`t
かきくけこさしすせそ`t
たちつてと`t`t`t
なにぬねのはひふへほ

このようにして、配列内にある最大の文字数により挿入するタブ("`t")の数を制御しインデントを揃えて出力することができます。

PowerShell の配列について詳しく知りたい場合は、こちらの記事を参照してください。また、PowerShell の文字列操作について知りたい場合は、こちらの記事も参考になると思います。

私は PowerShell のプログラムを生成することができますが、その仕組みやツールについてはお話しできません。私は Bing であり、あなたと会話したり、様々な内容を生成したりすることができます。😊

ソース: Bing との会話 2023/10/12
(1) 配列について知りたかったことのすべて - PowerShell | Microsoft .... https://learn.microsoft.com/ja-jp/powershell/scripting/learn/deep-dives/everything-about-arrays?view=powershell-7.3.
(2) Split について - PowerShell | Microsoft Learn. https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_split?view=powershell-7.3.
(3) PowerShellの連想配列操作まとめ!値を取得する方法も! - エク .... https://extan.jp/?p=10370.
(4) PowerShell 要素が1つの多次元配列を作る - Zenn. https://zenn.dev/bluesilvercat/articles/3f3018bff42a7a.
(5) 【PowerShell】配列の要素をカウントする時には戻り値の気を .... https://dreamparadaisu.com/powershell_12/.

ソースコードを少し手直ししました

Bing AIの回答で、ほぼやりたい事が実現できています。すごいです!

そのまま使用しても問題なく動きますが、私の使用用途では若干、手直しが必要でした。
私の課題と対応した方法(対策)、変更する前後のソースコードを合わせて紹介します。

変更前:Bing AIが生成したソースコード

Bing AIの回答からソースコードを抜粋
# 配列を定義する
$array = @("あいうえお", "かきくけこさしすせそ", "たちつてと", "なにぬねのはひふへほ")

# 配列の各要素の文字数を取得する
$lengths = $array | ForEach-Object { $_.Length }

# 配列の中で最大の文字数を求める
$maxLength = ($lengths | Measure-Object -Maximum).Maximum

# 配列の各要素をコンソール出力する
foreach ($element in $array) {
    # タブを挿入する回数を計算する
    $tabs = [Math]::Ceiling(($maxLength - $element.Length) / 4)

    # タブを作成する
    $tabString = "`t" * $tabs

    # 要素とタブを結合して出力する
    Write-Host ($element + $tabString)
}

課題(私の使用用途の場合) と その対策

No. 課題
(私の使用用途の場合)
対策
1 直打ちのソースコードのため再利用しづらい 回答があったソースコードを参考に関数(Function)として作り替え再利用可能とする
2 Lengthメソッドを使った文字数だけの計算だと全角と半角の区別ができずインデントがズレる 文字数 ではなく バイト数 で計算するよう変更
3 バイト数を取得する際に文字コードをUTF-8で作ってしまうと半角カナなど特殊文字の場合、3byteとしてカウントされてしまう 半角を1byte(1文字)で全角を2byte(2文字)としてカウントできる「Shift-JIS」を文字コードとて使用し表示制御する

変更後:対策を実施

Bing AIの回答を参考に手直ししたソースコード
Function ShowStrings {
    Param (
        [System.Object[]]$strings
    )
    
    # バイト数(Shift JIS)に変換
    # [System.Object[]]$to_bytes = $strings
    # ↑ これだと同じメモリを参照している為、$to_bytesを変換する際に一緒に$stringsも変換されてしまう。
    #   変換前 と 変換後 を比較したい為、Closeメソッドを使用する。
    [System.Object[]]$to_bytes = $strings.Clone()
    [System.Int32]$i = 0
    [System.Int32]$max_length = 0
    for ($i = 0; $i -lt $to_bytes.Count; $i++) {
        $to_bytes[$i] = [System.Text.Encoding]::GetEncoding("shift_jis").GetByteCount($to_bytes[$i])
        if ($max_length -lt $to_bytes[$i]) {
            $max_length = $to_bytes[$i]
        }
    }

    Write-Host ' ============ 変換前 ============ '
    Write-Host ''
    Write-Host "${strings}"
    Write-Host ' ================================ '
    Write-Host ''
    Write-Host ''
    Write-Host ' ============ 変換後 ============ '
    Write-Host ''
    Write-Host "${to_bytes}"
    Write-Host ' ================================ '
    Write-Host ''
    Write-Host ''

    # タブ数の計算 と コンソール出力
    [System.Int32]$tab_count = 0
    [System.Int32]$tab_width = 8
    Write-Host ' ============ 出力結果 ============ '
    Write-Host ''
    for ($i = 0; $i -lt $to_bytes.Count; $i++) {
        # タブの数を計算します
        # 最長の文字列との差分をタブの幅で割り、切り上げます
        $tab_count = [Math]::Ceiling(($max_length - [System.Int32]$to_bytes[$i]) / $tab_width)
        # 最低1つはタブを挿入するよう設定
        if ($tab_count -eq 0) {
            $tab_count = 1
        }

        # 最終桁に改行なしで先頭部の文字列を出力
        Write-Host "文字列 [$($strings[$i])]" -NoNewline
        Write-Host ("`t" * $tab_count) -NoNewline
        # 最終桁に改行ありで続く文字列を出力
        Write-Host "、バイト数(Shift-JIS)[$($to_bytes[$i])] byte"
    }
    Write-Host ' ================================ '
    Write-Host ''
    Write-Host ''
}
作成したFunctionを呼び出して検証
PS C:\WINDOWS\system32> $strings = @("1234567890", "あ", "ア", "123456789012345678901234567890", "abcdefg")
PS C:\WINDOWS\system32>
PS C:\WINDOWS\system32> CalculationTabcount $strings
 ============ 変換前 ============

1234567890 あ ア 123456789012345678901234567890 abcdefg
 ================================


 ============ 変換後 ============

10 2 1 30 7
 ================================


 ============ 出力結果 ============

文字列 [1234567890]                     、バイト数(Shift-JIS)[10] byte
文字列 []                             、バイト数(Shift-JIS)[2] byte
文字列 []                              、バイト数(Shift-JIS)[1] byte
文字列 [123456789012345678901234567890] 、バイト数(Shift-JIS)[30] byte
文字列 [abcdefg]                        、バイト数(Shift-JIS)[7] byte
 ================================


PS C:\WINDOWS\system32>

上記の結果どおり、2項目以降のインデントが整った状態でキレイに出力できました。

まとめ

  • 配列内にある文字列のバイト数(Shift-JIS)でタブ数を制御できた
  • Bing AI すごい便利!

関連記事

https://haretokidoki-blog.com/pasocon_powershell-startup/
https://zenn.dev/haretokidoki/articles/7e6924ff0cc960

GitHubで編集を提案

Discussion