初めてのPowerShell

初めてPowerShellを勉強しました。忘れてしまいそうなので、基本的な構文をメモとして残します。

# 用語

|用語|意味|
|:---|:---|
|オブジェクト|ファイル1個（1行）が1オブジェクトです|
|プロパティ|NameとかLengthの項目がプロパティです|
|コマンドレット|PowerShellでの命令を言います|

# 元データ

下のフォルダを対象として、サンプルを記載します。カレントディレクトリを見たい時はコマンドレット`Get-ChildItem`を使います。

```
> Get-ChildItem
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/05/01     12:00             20 test1.txt
-a----        2021/05/01     12:01             10 test2.txt
-a----        2021/05/01     12:02             30 test3.csv
```

# オブジェクトの抽出

ファイル名（プロパティ`Name`）に`txt`を含むオブジェクトだけ抽出します。

```
> Get-ChildItem | Where-Object Name -like '*txt*'
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/05/01     12:00             20 test1.txt
-a----        2021/05/01     12:01             10 test2.txt
```

更新日時（プロパティ`LastWriteTime`）が`2021/05/01 12:01`より大きいオブジェクトだけ抽出します。

```
> Get-ChildItem | Where-Object LastWriteTime -gt '2021/05/01 12:01'
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/05/01     12:02             30 test3.csv
```

最初の1行を抽出します。

```
> Get-ChildItem | Select-Object -First 1
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/05/01     12:00             20 test1.txt
```

最後の1行を抽出します。

```
> Get-ChildItem | Select-Object -last 1
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/05/01     12:02             30 test3.csv
```

# ソート

プロパティ`Length`で並び替えます。

```
> Get-ChildItem |  Sort-Object Length
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/05/01     12:01             10 test2.txt
-a----        2021/05/01     12:00             20 test1.txt
-a----        2021/05/01     12:02             30 test3.csv
```

プロパティ`Length`で逆順に並び替えます。

```
> Get-ChildItem |  Sort-Object Length -Descending
Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2021/05/01     12:02             30 test3.csv
-a----        2021/05/01     12:00             20 test1.txt
-a----        2021/05/01     12:01             10 test2.txt
```

# プロパティの抽出と確認

`Name`と`Length`のプロパティのみ取り出します。出力の順番も決定します。

```
> Get-ChildItem | Select-Object Name,Length

Name      Length
----      ------
test1.txt     20
test2.txt     10
test3.csv     30
```

プロパティの確認をします。表示されなかったプロパティも見ることができます。

```
> Get-ChildItem | Get-Member         
Name                      MemberType     Definition
----                      ----------     ----------
・・・（省略）・・・
Extension                 Property       string Extension {get;}
・・・（省略）・・・
Length                    Property       long Length {get;}
Name                      Property       string Name {get;}
・・・（省略）・・・
```

# 集計

オブジェクト数を数えます。

```
> Get-ChildItem | Measure-Object | Select-Object Count
Count
-----
    3
```

プロパティ`Length`の合計を数えます。

```
> Get-ChildItem | Measure-Object Length -sum | Select-Object sum
Sum
---
 60
```

拡張子（プロパティ`Extension`）毎に集計します。

```
> Get-ChildItem | Group-Object Extension
Count Name                      Group
----- ----                      -----
    2 .txt                      {test1.txt, test2.txt}
    1 .csv                      {test3.csv}
```

# 変数

変数は`$`から始まります。型宣言はありません。

```
> $a = 1
> $a + 1
2
```

変数に文字も入れることができます。計算が行われる時には、左側の型で計算が行われます。上の例では、[数値]＋[数値]なので[数値]、下の例では、[文字]+[数値]なので[文字]になります。

```
> $b = `abc`
> $b + 1
abc1
```

配列も使えます。

```:1次元配列
> $c = @( 1,2,3 )
```

```:2次元配列
> $c = @(@(1,2),@(3,4))
```

```:使い方（2次元配列の場合）
> $c[0][1]
2
```

連想配列も使えます。`;`を使い1行で書く方法と、1行づつ分けて書く方法があります。

```:1行で書く方法
> $c = @{'a'=1;'b'='abc'}
```

```:1行づつ分けて書く方法
> $c = @{
'a'=1
'b'='abc'
}
```

```:使い方
> $c['b']
abc
```

また、変数にはオブジェクトも入れることができます。下の例では、`Ge-ChildItem`で取得した3つのオブジェクト（配列）を変数`$c`に入れています。

```
> $c = Get-ChildItem | Select-Object Name,Length
> $c
Name      Length
----      ------
test1.txt     20
test2.txt     10
test3.csv     30
```

変数`$c`の最初のオブジェクトを取り出します。

```
> $c[0]
Name      Length
----      ------
test1.txt     20
```

変数`$c`から特定のプロパティを取り出すこともできます。

```
> $c.Name
test1.txt
test2.txt
test3.csv
```

# 文字列と操作

文字は`'`か`"`で囲みます。

```
> 'Start'
Start
```

`"`で囲まれた文字の中に変数がある場合は、その場所にその変数の値が入ります。

```
> "3 - 2 = $a"
3 - 2 = 1
```

`'`で囲まれた文字はそのまま使われます。

```
> '3 - 2 = $a'
3 - 2 = $a
```

文字列の連結には`+`を使います。

```
> $b+'def'
abcdef
```

文字列の長さを調べるには`Length`を使います。

```
> 'abcdef'.Length
6
```

文字を取り出すには、`Substring`を使います。

```:2文字目から3文字分を取り出す場合
> 'abcdef'.Substring(1,3)
bcd
```

検索で文字列の取り出すには下のような正規表現の判定を使った方法があります。この判定で見つかった文字列が`$Matches`に勝手に入ります。なお、複数該当していても最初の1個しか入らないので注意してください。（下の例では`f456`も該当するが表示されていない）

```
> 'abc123def456' -match '\w\d+'
True
> $Matches
Name                           Value
----                           -----
0                              c123
```

# ファイルの入出力

変数`$b`を`output.txt`として出力（上書き）します。ファイルの中身を見たい時はコマンドレット`Get-Content`を使います。

```
> $b | Set-Content .\output.txt
> Get-Content .\output.txt
abc
```

ファイル`output.txt`に`End`を追記します。

```
> "End" | Add-Content .\output.txt
> Get-Content .\output.txt
abc
End
```

変数`$c`をcsvファイル`test.csv`として出力します。

```
> $c | Export-Csv .\test.csv
> Get-Content .\test.csv
#TYPE Selected.System.IO.FileInfo
"Name","Length"
"test1.txt","20"
"test2.txt","10"
"test3.csv","30"
```

csvファイル`test.csv`を変数`$d`に入れます。

```
> $d = Import-Csv .\test.csv
> $d
Name      Length
----      ------
test1.txt 20
test2.txt 10
test3.csv 30
```

# データ変換

変数`$c`をJSON形式に変換します。

```
> $c | ConvertTo-Json | Set-Content .\test.json
> Get-Content .\test.json
[
    {
        "Name":  "test1.txt",
        "Length":  20
    },
    {
        "Name":  "test2.txt",
        "Length":  10
    },
    {
        "Name":  "test3.csv",
        "Length":  30
    }
]
```

JSON形式を読み込みます。

```
> Get-Content .\test.json | ConvertFrom-Json
Name      Length
----      ------
test1.txt     20
test2.txt     10
test3.csv     30
```

変数`$c`をHTML形式に変換します。

```
> $c | ConvertTo-Html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>HTML TABLE</title>
</head><body>
<table>
<colgroup><col/><col/></colgroup>
<tr><th>Name</th><th>Length</th></tr>
<tr><td>test1.txt</td><td>20</td></tr>
<tr><td>test2.txt</td><td>10</td></tr>
<tr><td>test3.csv</td><td>30</td></tr>
</table>
</body></html>
```

# 条件分岐

コマンドレット`if`を使います。

```:elseを使った例
> if ($a -eq 1) { '等しい' } else { '等しくない' }
等しい
```

```:elseifを使った例
> if ($a -eq 2) { '２と等しい' } elseif ($a -eq 1) { '１と等しい' } else { '等しくない' }
１と等しい
```

コマンドレット`switch`もあります。

```
> switch ($a)
{
2 { '２と等しい' }
1 { '１と等しい' }
default { '等しくない' }
}
１と等しい
```

PowerShellの比較演算子を下に記載します。

|意味|PowerShellの記入例|Pythonの記入例|
|:--|:--|:--|
|等しい|\\$a -eq \\$b|a == b|
|等しくない|\\$a -ne \\$b|a != b|
|大きい|\\$a -gt \\$b|a > b|
|以上|\\$a -ge \\$b|a >= b|
|以下|\\$a -le \\$b|a <= b|
|小さい|\\$a -lt \\$b|a < b|
|ワイルドカード|'abc' -like '\*b*'|モジュール`glob`を使用する|
|ワイルドカードの否定|'abc' -notlike '\*b*'|モジュール`glob`を使用する|
|正規表現|'abc123' -match '^\w*\d*$'|モジュール`re`を使用する|
|正規表現の否定|'abc123' -notmatch '^\w*\d*$'|モジュール`re`を使用する|
|配列に含まれる|@(1,2,3) -contains 1|1 in [1,2,3]|
|配列に含まれない|@(1,2,3) -notcontains 1|1 not in [1,2,3]|

# ループ

コマンドレット`ForEach-Object`を使い、変数`$c`のプロパティ`Name`の値を1つづつ表示します。
`ForEach-Object`がパイプから受け取ったオブジェクトは変数`$_`に格納されます。その変数`$_`のプロパティ`Name`を取り出しています。

```
> $c | ForEach-Object { $_.Name }
test1.txt
test2.txt
test3.csv
```

# おわりに

次回はPowerShellを使ったスクリプトを作ってみたいと思います。、