Bashスクリプト→PowerShellスクリプト　移植レシピ

# 前提

- macOSやLinuxにガチガチに依存したBashスクリプトをWindowsでも動かしたい
- 元のテキストファイルを`grep`・`sed`などで加工している
- 入出力はUTF-8 + LF

# エラーが発生したらスクリプトを終了

```bash
set -eu
```

↓

```powershell
$ErrorActionPreference = "Stop"
```

# 入力・分割

```powershell
$Content = Get-Content -Encoding UTF8 -Raw ./test.txt
```

```powershell
$ContentLines = (Get-Content -Encoding UTF8 -Raw ./test.txt).Split("`n") # CRは各行末に残る・末尾の改行も残る
$ContentLines = Get-Content -Encoding UTF8 ./test.txt # CRLFも区切り文字・末尾の改行は残らない
```

`Get-Content` = `cat`

`-Encoding UTF8`の`F`と`8`の間に`-`を入れないように注意してください。

# 出力

```powershell
[IO.File]::WriteAllText("$PWD\test2.txt", $Content)
```

```powershell
[IO.File]::WriteAllText("$PWD\test2.txt", $ContentLines -join "`n")
```

注意: `WriteAllLines`は、行末をCRLFに変えます。

# コマンドオプション

Bashでオプション解析を本気でやろうとすると、かなり骨が折れます。

# 抽出

## 特定文字で区切ったフィールドの抽出

```bash
USER_NAME=$(awk '{ print $3 }' <<<"$LINE")
```

↓

```powershell
$Fields = $Line -Split " +"
$UserName = $Fields[2]
```

## 番号からの行抽出

### 配列インデクサを使った抽出

```bash
$CONTENT_LINES | head -n 1
$CONTENT_LINES | tail -n 1
$CONTENT_LINES | head -n 3
$CONTENT_LINES | tail -n 3
cat foo.txt | head -n 2
```

↓

```powershell
$ContentLines[0]
$ContentLines[-1]
$ContentLines[0..2]
$ContentLines[-3..-1]
(Get-Content -Encoding UTF8 foo.txt)[0..1]
```

### Select-Objectを使った抽出

```bash
$CONTENT_LINES | head -n 5
$CONTENT_LINES | head -n -5
$CONTENT_LINES | tail -n 5
$CONTENT_LINES | tail -n +5 # 5番目「から出力」
```

↓

```powershell
$ContentLines | Select-Object -First 5
$ContentLines | Select-Object -SkipLast 5
$ContentLines | Select-Object -Last 5
$ContentLines | Select-Object -Skip 4 # 4番目「までを飛ばす」
```
`Select-Object` = `select`

## grepのような行マッチ抽出

```bash
$CONTENT_LINES | fgrep foo
$CONTENT_LINES | igrep foo

$CONTENT_LINES | grep "^[Ff]oo"

$CONTENT_LINES | grep "^foo"
$CONTENT_LINES | grep "foo$"
```

↓

```powershell
$ContentLines | Where-Object { $_.Contains("foo") }
$ContentLines | Where-Object { $_ -imatch "foo" }

$ContentLines | Where-Object { $_ -match "^[Ff]oo" } # 正規表現
$ContentLines | Where-Object { $_ -like "[Ff]oo*" } # glob形式

$ContentLines | Where-Object { $_.StartsWith("foo") }
$ContentLines | Where-Object { $_.EndsWith("foo") }
```

`Select-String` を使った別解

```powershell
$ContentLines | Select-String -SimpleMatch -CaseSensitive foo
$ContentLines | Select-String foo

$ContentLines | Select-String -CaseSensitive ^[Ff]oo

$ContentLines | Select-String -CaseSensitive ^foo
$ContentLines | Select-String -CaseSensitive foo$
```

`Where-Object` = `?` or `where`
`Select-String` = `sls`

### おまけ: ファイル検索

```bash
grep [fF]oo *.txt
```

↓

```bash
Select-String [fF]oo -CaseSensitive *.txt
```

# 置換

```powershell
$Content.Replace("old", "new")
$Content -replace "[Oo]ld", "new" 
$ContentLines | ForEach-Object { $_ -replace "^old", "new" }
```

`ForEach-Object` = `%` or `foreach`

## パターンマッチの使用

```bash
ACCESS_PATH=$(sed -E 's/^[A-Z]+ ([^ ]+) .+$/\1/' <<<"GET /index.html HTTP/1.1")
```

↓

```powershell
$AccessPath = "GET /index.html HTTP/1.1" -replace "^[A-Z]+ ([^ ]+) .+$", "`$1"
```

PowerShellの場合、パターンマッチの置換先は`$`をエスケープする必要があります。さもないと、変数展開により、「`$1`」という名前の変数に展開されてしまいます。

# 特定のコマンドの存在を確認

```bash
if !type foo >/dev/null 2>&1; then
  echo "foo not found" >&2
  exit 1
fi
````
↓

```powershell
if(!(Get-Command foo -ErrorAction Ignore)) {
  Write-Error "foo not found"
  exit 1
}
```

# 配列・連想配列

## 配列宣言

```bash
empty=()
local -a empty2=()
array1=(1)
array2=(1 2)
```

```powershell
$empty = @()
$array1 = ,1
$array2 = @(1)
$array3 = 1,2
$array4 = (1,2)
$array5 = @(1,2)
$array6 = ,1,2
```

## 配列拡張

```powershell
$a = ,1
$a += 2
$a += ,3,4
```


## 連想配列宣言

```bash
declare -A hash1
hash1=(
["a"]=1
["b"]=2
["c"]=3
)
```

```powershell
$hash1 = @{a = 1; b = 2; c = 3}
```

## 連想配列アクセス

```powershell
$hash1["a"] -eq 1
$hash1.b -eq 2
($hash1 | Select-Object -ExpandProperty c) -eq 3
```

# リンク抽出

```bash
# <a href='...'>は不可
curl -LsSf http://foo.bar/ | egrep -i -o '<a( [^>])? href="[^"]+"( [^>]+)?>' | grep -oP '(?<=href=")[^"]+(?=")'
```

```powershell
Invoke-WebRequest http://foo.bar/ | Select-Object -ExpandProperty Links
(Invoke-WebRequest http://foo.bar/).Links
```

`Invoke-WebRequest` = `iwr` (PowerShell5では`curl`・`wget`も)