Powershell入門 - 04.変数

# 1. 変数の基本
この記事ではPowershellをよりダイナミックに活用するために、変数の使い方を学んでいきます。
一番最初に学ぶ言語がPowershellである人は少ない(はず)なので、少し説明を端折っています。

## 1.1. 変数の利用方法
まずは変数の宣言・初期化についてです。
Powershell上で以下の処理を書いてみましょう。
```powershell
$a = 1
Write-Host $a
```
`$a`の部分が変数になります。
変数は必ず`$(ドルマーク)`をつけて使用します。
PHPを知っている人はPHPと同じ書き方だと思えば良いです・

また、`Write-Host`というコマンドレットが新たに登場しましたが、これは画面へ出力するためのコマンドレットです。
他の言語で言う、print関数やecho関数だと思うと良いでしょう。

変数には色々なタイプのデータを入れることができます。
```powershell
$a = 15       # 整数
$b = 0.5      # 小数
$c = "Hello"  # 文字列

Write-Host $a.GetType()     # System.Int32
Write-Host $b.GetType()     # System.Double
Write-Host $c.GetType()     # System.String
```
Powershellは`動的型付け言語`であり、変数の型を宣言時に書く必要はなく自動で判別してくれます。
`$変数名.GetType()`を使うと、変数の型を取得できます。

## 1.2. 数値や文字列の演算
変数に格納された数値や文字列に対して、演算子を使った処理が可能です。
```powershell
Write-Host ($a * 3)          # 45
Write-Host ($b + 1.2)        # 1.7
Write-Host ($c + 4)          # Hello4
Write-Host ($c * 4)          # HelloHelloHelloHello
```

---
#### 例題1.
変数aと変数bをそれぞれ25,4.0で定義しなさい
変数cに変数aとbの掛け算の結果を格納し、変数cの値を表示しなさい。
また、変数cの型を表示しなさい

---

## 1.3. 配列の操作
次は配列について見ていきましょう。

### 1.3.1. 配列の初期化
配列の初期化方法は以下の通りです。
```powershell
$a = "a","b","c","d","e"  # 要素をカンマ区切りで列挙する
$b = 1..5                 # 連続した数値の場合、「..」で定義可能(1～5までの配列)
$c = @()                     # 空の配列の定義
```
配列を初期化する場合、カンマ区切りで要素を並べていきます。
連続した数値を定義したい場合は便利な記法（..）が用意されているので活用しましょう。
また、`@()`のみを書けば空の配列を定義可能です。

### 1.3.2. 配列の要素の参照・代入
要素の参照・代入方法は他の言語と共通しています。
```powershell
Write-Host $b[3]        # 要素の参照
$a[2] = "C"             # 要素の代入
```
---
#### 例題2.
以下の文字列からなる配列を定義しなさい。
- "さんま","まくら","あいさつ","こいぬ","ぬりえ"

配列の要素を手前から順番に読むと「しりとり」が成立するように、配列の一部を修正しなさい。  
その後、配列を表示しなさい。

#### 例題3.
1から10までの連続した整数を**大きい順に**格納した配列を定義し、表示しなさい。

---

### 1.3.3. 配列のスライス
配列の一部分を切り出した、新しい配列を取得する方法を`配列のスライス`といいます。
配列の参照のように`[]`に要素の添字を指定しますが、複数個の要素をまとめて指定します。
```powershell
$arr = 10..20
Write-Host $arr[1, 3, 5]      # 1,3,5番目の要素を切り出し
Write-Host $arr[1, 4, 2, 8]   # 順番を入れ替えてもOK
Write-Host $arr[2..7]         # 連続した要素は「..」を使うと便利
```

---
#### 例題4.
以下の配列を定義しなさい。
```powershell
$d = 100..200
```
スライスを使って、値が150以上180以下の要素をすべて取得しなさい。

---

### 1.3.4. 配列の要素の追加・配列の結合
配列に対して要素を追加する場合、`+演算子`が使えます。
```powershell
$a = 1..10
$a = $a + 11     # 「配列 + 要素」で配列の末尾に要素が追加された配列が返される
$a += 11         # 「+=」演算子を使っても書ける
```

また、配列の結合についても`+演算子`が使えます。
```powershell
$a = 1..10
$b = 11..20
Write-Host ($a + $b)
```

---

#### 例題5.
空の配列xとyを用意しなさい。
それぞれに以下の値を順番に格納しなさい。
※要素の結合を使うこと

- x: 1,2,3
- y: 4,5,6

また、xとyを結合した新しい配列zを定義し、結果を表示しなさい。

---

### 1.3.5. 配列の長さを取得
配列の長さは`$配列名.Length`で取得することができます。
また、Lengthのエイリアス(別名)として、`Count`が定義されているので、こちらでもOKです。
```powershell
$a = 0..10
Write-Host $a.Length
Write-Host $a.Count    # $a.Lengthと結果は同じ
```
---
#### 例題6.
例題5で定義した配列の長さを求めなさい。

---

# 2. コマンドレットと変数
ここまではどのプログラミング言語でも習うような変数の基本について説明しました。
この変数をPowershellのコマンドレットと組み合わせて使っていく方法を学んでいきましょう。

## 2.1. コマンドレットの結果を変数に格納する
まずはいつもの資料フォルダへカレントディレクトリを移動します。
```powershell
Set-Location D:\powershell\data
```

ここでGet-ChildItemを使用すると、dataフォルダ直下のファイル・フォルダの一覧が取得できます。
このGet-ChildItemの結果を変数に代入してみましょう。

```powershell
(base) PS D:\powershell\data> $gci = Get-ChildItem
(base) PS D:\powershell\data> $gci


    ディレクトリ: D:\powershell\data


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-r---        2022/08/10      9:06                sampleFolder1
d-r---        2022/08/10     13:19                sampleFolder2
-a----        2021/02/25      8:33              0 sample1.py
-a----        2022/08/10      9:08              6 sample1.txt
```

これまでであれば、Get-ChildItemを実行した瞬間に画面に結果が表示されていました。
しかし、変数に代入することによって、Get-ChildItemの結果は変数が受け取ったため画面に表示されません。
その後、変数gciを出力してあげることで、結果が表示されました。

この例では、変数を使うメリットがあまりありませんね。
では、次の例ではどうでしょうか。
```powershell
(base) PS D:\powershell\data> $gl = Get-Location
(base) PS D:\powershell\data> Get-ChildItem -Path $gl


    ディレクトリ: D:\powershell\data


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-r---        2022/08/10      9:06                sampleFolder1
d-r---        2022/08/10     13:19                sampleFolder2
-a----        2021/02/25      8:33              0 sample1.py
-a----        2022/08/10      9:08              6 sample1.txt
```

変数glにはGet-Locationの結果(カレントディレクトリのフォルダパス)が格納されています。
このglをGet-ChildItemのPathパラメータにわたすことで、$glフォルダ直下のフォルダ、パスの一覧が取得できました。
変数を使うことで、コマンドレットどうしを連携させることができます。
これが変数を使うメリットです。

## 2.2. 要素へのアクセス方法
コマンドレットの連携について見ていく前に、コマンドレットを格納した変数についてもう少しだけ説明します。
もう一度、$gciの中身を確認してみます。
```powershell
(base) PS D:\powershell\data> $gci


    ディレクトリ: D:\powershell\data


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-r---        2022/08/10      9:06                sampleFolder1
d-r---        2022/08/10     13:19                sampleFolder2
-a----        2021/02/25      8:33              0 sample1.py
-a----        2022/08/10      9:08              6 sample1.txt
```

powershellのコマンドレットが返す結果は、すべて**オブジェクト**となっています。
ここでは、オブジェクト指向について詳しく説明するつもりはありません。
要は「コマンドレットが返した値自体が、いくつかの属性を持っている」と思ってください。

例えば、以下の処理を試してみましょう。
```powershell
(base) PS D:\powershell\data> $gci.Name
sampleFolder1
sampleFolder2
sample1.py
sample1.txt
```
`$gci`の表の右側にあったName列の値だけ取得できました。
つまり、「`$gci`はNameという属性を持っていて、`$gci.Name`とすることでその値にアクセスできる」ということです。
当然、Mode、LastWriteTime、Lengthにも同じようにアクセスできます。

上の例では、表の列だけを取得しましたが、行だけ取得することも可能です。
こちらは、結果が配列に格納されていると思うと理解しやすいです。
```powershell
(base) PS D:\powershell\data> $gci[2]


    ディレクトリ: D:\powershell\data


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/02/25      8:33              0 sample1.py
```
これは、表の2行目の値のみ取得した例になります。

このようにコマンドレットの結果がオブジェクトだと思うことによって、そのコマンドレットの結果がもつ色々なデータ(属性)にアクセスできます。
この事実は、コマンドレットどうしを連携させるときに非常に便利です。

---
#### 例題6.
以下のコマンドレットの結果を変数に格納し、その結果のName属性だけ表示しなさい。

```powershell
Get-Command -CommandType Cmdlet
```

#### 例題7.
変数gciの2行目のName属性のみを取得しなさい。

---

## 2.3. 変数を介したコマンドレットの連携
では、コマンドレットどうしを連携させる例を見ていきましょう。
(需要があるかはわかりませんが)カレントディレクトリの0番目のフォルダの中身を表示する場合の処理です。

```powershell
$gci = Get-ChildItem
$sf1 = $gci[0]
Get-ChildItem -Path $sf1.Name
```
上記を理解するには、1行ずつ変数の中身を確認すると良いでしょう。
1行目では、カレントディレクトリのフォルダ・ファイルの一覧を取得しています。
2行目でその一覧の0番目の要素を取得しました。
中身は以下のとおりです。
```powershell
(base) PS D:\powershell\data> $sf1


    ディレクトリ: D:\powershell\data


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-r---        2022/08/10      9:06                sampleFolder1
```
3行目で、このsampleFolder1に含まれるファイル・フォルダの一覧を取得しています。
このように書くことで、変数を介してコマンドレットどうしが連携できています。

実際の処理では、「0番目の要素」のように添え字を直接指定するケースは少ないです。
代わりに、if文やfor文などと組み合わせて、すべてのフォルダに対して処理を行っていきます。

#### 例題8
上記の例を参考にして、カレントディレクトリ(dataフォルダとします)をGet-ChildItemで表示した場合の3番目のファイルの中身を表示しなさい。


### 余談
コマンドレットの結果はオブジェクトだと説明しました。
では、オブジェクトではない場合、どのような違いやデメリットがあるのでしょう。

その説明をするために、以下の2つの変数を用意します。
```powershell
$ipconf_old = ipconfig
$ipconf_new = Get-NetIPConfiguration
```
ipconfigはコマンドプロンプトという従来のシェルで使えるコマンドです。
ipconfigはPCのネットワークの一覧を表示します。
一方の、Get-NetIPConfigurationはpowershellに対応したipconfigだと思ってください。

まずは、両方の出力結果を見ていきます。
※具体的なアドレスの値はマスクしています。

#### 従来の出力
```powershell
(base) PS D:\powershell\data> $ipconf_old

Windows IP 構成

(中略)

イーサネット アダプター イーサネット:

   接続固有の DNS サフィックス . . . . .:
   リンクローカル IPv6 アドレス. . . . .: XXXX::XXXX:XXXX:XXXX:XXXX%XX
   IPv4 アドレス . . . . . . . . . . . .: XXX.XXX.XXX.XXX
   サブネット マスク . . . . . . . . . .: XXX.XXX.XXX.XXX
   デフォルト ゲートウェイ . . . . . . .: XXX.XXX.XXX.XXX

イーサネット アダプター イーサネット 2:

   メディアの状態. . . . . . . . . . . .: メディアは接続されていません
   接続固有の DNS サフィックス . . . . .:

(後略)
```

#### Powershellのコマンドレットの出力
```powershell
(base) PS D:\powershell\data> $ipconf_new

(中略)

InterfaceAlias       : イーサネット
InterfaceIndex       : XX
InterfaceDescription : ??????????????????????
NetProfile.Name      : ネットワーク 10
IPv4Address          : XXX.XXX.XXX.XXX
IPv6DefaultGateway   :
IPv4DefaultGateway   : XXX.XXX.XXX.XXX
DNSServer            : XXX.XXX.XXX.XXX
                       XXX.XXX.XXX.XXX

InterfaceAlias       : イーサネット 2
InterfaceIndex       : XX
InterfaceDescription : ??????????????????????
NetAdapter.Status    : Disconnected

(後略)

```

出力結果が英語になったなどの違いはありますが、表示されている内容に違いは殆ど見られません。
では、以下の処理を両者で行った場合の違いを見ていきましょう。

#### 従来の出力
```powershell
(base) PS D:\powershell\data> $ipconf_old[1]
Windows IP 構成
```
#### Powershellのコマンドレットの出力
```powershell
(base) PS D:\powershell\data> $ipconf_new[1]
InterfaceAlias       : イーサネット
InterfaceIndex       : XX
InterfaceDescription : ??????????????????????
NetProfile.Name      : ネットワーク 10
IPv4Address          : XXX.XXX.XXX.XXX
IPv6DefaultGateway   :
IPv4DefaultGateway   : XXX.XXX.XXX.XXX
DNSServer            : XXX.XXX.XXX.XXX
                       XXX.XXX.XXX.XXX
```

いかがでしょうか。
Powershellの方から説明すると、Get-NetIPConfigurationはオブジェクトになっているため、この結果がもつ各要素や属性にアクセスできます。
つまり、`$ipconf_new[1]`は「1番目のネットワークの情報」になります。

一方で、従来の出力は**ただの文字列**です。
そのため、`$ipconf_old[1]`は「出力結果の1行目」になります。

もし、1番目のネットワークのIPアドレスが知りたいとなった場合、従来の方法だと非常に不便なことになります。

```powershell
Write-Host $ipconf_old[13][38..47]               # 文字列の行番号と何文字目かを調べる必要がある
Write-Host $ipconf_new[1].IPv4Address.IPAddress  # ネットワーク番号と属性がわかれば良い
```

powershellのコマンドレットの結果がオブジェクトであるメリットが伝わったでしょうか？

Prev : [03.コマンドの基本②](./ce474e3eee7d827ce55d)
Next : [05. スクリプト化](./436047504e2365c5bd03)

[目次へ戻る](./926b8053a031539437a3)