zsh文字列操作まとめ

#### 代入

```console
$ a='こんにちは、世界'
```

#### 比較

```console
$ if [ "$a" = "hoge" ]; then echo "yes"; fi
```

### 別の文字列を含むか

```console
$ if [[ "$a" == *og* ]]; then echo "yes"; fi
```

#### 長さ

```console
$ echo $#a
8
```

#### n文字目を取得（1始まり）

```console
$ echo $a[1]
こ
```

#### 部分文字列（n文字目から長さl）

```console
$ echo ${a:2:3}
にちは
```

#### 部分文字列（n文字目からm文字目。終端含む）

```console
$ echo ${c[2,3]}
んに
```

#### 部分文字列（n文字目以降）

```console
$ echo ${a:2}
にちは、世界
```

#### 部分文字列（先頭n文字）

```console
$ echo ${a::2}
こん
```

#### 部分文字列（末尾n文字）

```console
$ echo ${a: -2}
世界
```

#### 置換（最初の1個）
（検索文字列の`*`はワイルドカード）

```console
$ a='こんにちは、世界。こんにちは、世界'
$ echo ${a/にち/ばん}
こんばんは、世界。こんにちは、世界
```

#### 置換（全部）

```console
$ echo ${a//にち/ばん}
こんばんは、世界。こんばんは、世界
```

#### 前後の空白をtrim

```console
$ c='   Hello world   '
$ echo "[${${c##[[:space:]]##}%%[[:space:]]##}]"
[Hello world]
```

`${c##xxx}` で文字列cの先頭からパターンxxxにマッチする部分を削除する（最長マッチ）。
`${c%%xxx}` で文字列cの末尾からパターンxxxにマッチする部分を削除する（最長マッチ）。
`[[:space:]]##`はスペースの1個以上の繰り返し（`##`に1個以上の繰り返しという意味がある）。

#### 先頭の空白をtrim

```console
$ echo "[${c##[[:space:]]##}]"
[Hello world   ]
```

#### 末尾の空白をtrim

```console
$ echo "[${${c}%%[[:space:]]##}]"
[   Hello  world]
```

#### 大文字にする

```console
$ b='Hello world'
$ echo ${(U)b}
HELLO WORLD
```

#### 小文字にする

```console
$ echo ${(L)b}
hello world
```

#### ゼロパディング

```console
$ a=1; echo ${(l:4::0:)a}
0001

# 長い場合は切り詰められる
$ a=99999; echo ${(l:4::0:)a}
9999
```

#### splitして配列にする

```console
# カンマで分割
s='hoge,moge,sage'
a=(${(s:,:)s})

# コロンで分割したい場合は、他の記号で囲めば良い（下記はスラッシュにした）
s='hoge:moge:sage'
a=(${(s/:/)s})

# 改行で分割
a=(${(f)s})

# \0で分割
a=(${(0)s})

# zshの文法に従って単語に分割する
a=(${(z)s})
```

#### join

リテラルでjoin
```console
$ a=(hoge moge sage)
$ echo ${(j:/:)a}
hoge/moge/sage
```

変数でjoin

```console
$ a=(hoge moge sage)
$ b='/'
$ echo ${(pj:$b:)a}
hoge/moge/sage
```

改行でjoin

```console
$ a=(hoge moge sage)
$ echo "${(F)a}"
hoge
moge
sage
```

#### 正規表現でグルーピング

```console
if [[ "ab123cd456" =~ ([0-9]+)[^0-9]+([0-9]+) ]]; then
    echo "${MATCH}" # => 123cd456
    echo "${match[1]}" # => 123
    echo "${match[2]}" # => 456
fi
```

#### パターンマッチ判定

```console
$ [[ "$a" == *og* ]] && echo yes
yes
```

パターンマッチはglobとは異なり`*`が`.`や`/`にもマッチする。
```console
$ [[ ".zshrc" == * ]] && echo yes 
yes
```

パターンマッチでは下記の演算子が使える。`#`と`##`のおかげで拡張正規表現相当のことはできる。
```
*        任意の文字列。空文字列にもマッチ
?        任意の1文字
[abc]    a b c いずれか
[a-z]    aからzまでの1文字
[^abc]   a b c以外の1文字
[^a-z]   aからzまで以外の1文字にマッチ
<1-100>  1から100までの数
<->      任意の数
(...)    パターンのグループ化
x|y      パターンxまたはパターンy
^x       パターンx以外。setopt EXTENDED_GLOBが必要
x~y      パターンxにマッチし、パターンyにマッチしない。setopt EXTENDED_GLOBが必要
x#       パターンxの０回以上の繰り返し。setopt EXTENDED_GLOBが必要。12#は(12)#でなく1(2)#になることに注意
x##      パターンxの1回以上の繰り返し。setopt EXTENDED_GLOBが必要。12#は(12)#でなく1(2)#になることに注意
```


#### クォート

バックスラッシュでクォート

```console
$ a='a b"c'                      
$ echo "${(q)a}"
a\ b\"c
```

シングルクォートでクォート

```console
$ echo "${(qq)a}"
'a b"c'
```

ダブルクォートでクォート
```console
$ echo "${(qqq)a}"
"a b\"c"
```

#### ホームディレクトリを ~ に置換

```console
$ a='/home/aoyama/bin'
$ echo "${(D)a}"
~/bin
```

```console
$ print -v x -D "/home/aoyama/bin"     
$ echo $x
~/bin
```

（printは標準出力に出力するコマンドだが、オプション`-v x`で変数xに代入になる）

#### ~ をホームディレクトリに置換

`(e)`が使えるが、変数展開やコマンド置換もされてしまうので注意。

```console
$ a='~/bin'
$ echo "${(e)a}"
~/bin
```

変数が展開される。
```console
$ a='$SHELL'
$ echo "${(e)a}"
/bin/zsh
```

コマンド置換もされる。
```console
$ a='$(date)'
$ echo "${(e)a}"
Sat Nov  4 12:18:30 AM JST 2023
```

#### リテラル

公式ドキュメント：[Quoting](https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html#Quoting)

##### シングルクォート
バックスラッシュによるエスケープは一切効かない。

```console
$ print -r -- '\n'   
\n
```

##### ダブルクォート
`\` `'` `"` `$` `!` をバックスラッシュでエスケープできる。`\n` `\t`は改行・タブにならない。
（`!`のエスケープを忘れて履歴展開されてしまうのはやりがちなので注意）

```console
$ print -r -- "\""
"
```

#### ドル付きシングルクォート `$'...'`
`\n`で改行、`\t`でタブ、`\xXX` でASCIIコード、`\uxxxx`でユニコードコードポイントなどが使える。

```console
$ print -r -- $'a\nb' 
a
b

$ print -r -- $'\x41'
A

$ print -r -- $'\u3042'
あ
```