シェルスクリプトで文字列の一部を取り出す


:::note info
Alfredのワークフローなどで、スクリプトにzshをはじめました。初心者用にメモを残します
:::

# シェルスクリプトで文字列の一部を取り出す

文字列の一部を取り出すというのは、エクセルVBAのmid関数みたいな操作のことです
シェルスクリプトのパラメータ展開というものを利用します

書式
` ${パラメータ:開始位置:長さ} `


例
```zsh
 #日付の形式を変換する mmddyy　→ yyyy/mm/dd
 mmddyy='031122';echo 20${mmddyy:4:2}/${mmddyy:0:2}/${mmddyy:0:2}
 #→ 2022/03/11
```

※開始位置は先頭が0となります
※長さを省略すると文字列の最後まで切り取ります


### 雑談

開始位置や長さにマイナスが指定できるようなので、取りうる範囲を試してみました

<font color='red'>※以下の例で結果表示としてパラメータにリテラル"ABCED"を指定していますが、本当は変数でないとエラーになります</font>

### 開始位置がゼロ及び正の整数、長さが正の整数の場合
```zsh
#開始位置が0及び正の整数、長さが正の整数
str=ABCDE; for i in {0..4}; for j in {1..$((5-$i))}; do echo  '${"ABCDE":'$i':'$j'}='${str:$i:$j} ; done
#→ ${"ABCDE":0:1}=A
#→ ${"ABCDE":0:2}=AB
#→ ${"ABCDE":0:3}=ABC
#→ ${"ABCDE":0:4}=ABCD
#→ ${"ABCDE":0:5}=ABCDE
#→ ${"ABCDE":1:1}=B
#→ ${"ABCDE":1:2}=BC
#→ ${"ABCDE":1:3}=BCD
#→ ${"ABCDE":1:4}=BCDE
#→ ${"ABCDE":2:1}=C
#→ ${"ABCDE":2:2}=CD
#→ ${"ABCDE":2:3}=CDE
#→ ${"ABCDE":3:1}=D
#→ ${"ABCDE":3:2}=DE
#→ ${"ABCDE":4:1}=E
```
このパターンは直感的にわかりやすいですね


### 開始位置が負の整数、長さが正の整数の場合
```zsh
# 開始位置が負の整数、長さが正の整数の場合
str=ABCDE; for i in {-5..-1}; for j in {1..$(($i*-1))}; do echo  '${"ABCDE":'$i':'$j'}='${str:$i:$j} ; done
#→ ${"ABCDE":-5:1}=A
#→ ${"ABCDE":-5:2}=AB
#→ ${"ABCDE":-5:3}=ABC
#→ ${"ABCDE":-5:4}=ABCD
#→ ${"ABCDE":-5:5}=ABCDE
#→ ${"ABCDE":-4:1}=B
#→ ${"ABCDE":-4:2}=BC
#→ ${"ABCDE":-4:3}=BCD
#→ ${"ABCDE":-4:4}=BCDE
#→ ${"ABCDE":-3:1}=C
#→ ${"ABCDE":-3:2}=CD
#→ ${"ABCDE":-3:3}=CDE
#→ ${"ABCDE":-2:1}=D
#→ ${"ABCDE":-2:2}=DE
#→ ${"ABCDE":-1:1}=E
```
マイナスにすると末尾からのオフセットとなります
先頭位置の指定は先頭から0、1、2・・・ですが
末尾からの指定の場合は、末尾から-1、-2、-3となります

### 長さがゼロの場合
```zsh
#長さがゼロの場合
str=ABCDE; for i in {-5..4}; do echo  '${"ABCDE":'$i':0}='${str:$i:0} ; done
#→ ${"ABCDE":-5:0}=
#→ ${"ABCDE":-4:0}=
#→ ${"ABCDE":-3:0}=
#→ ${"ABCDE":-2:0}=
#→ ${"ABCDE":-1:0}=
#→ ${"ABCDE":0:0}=
#→ ${"ABCDE":1:0}=
#→ ${"ABCDE":2:0}=
#→ ${"ABCDE":3:0}=
#→ ${"ABCDE":4:0}=
```
長さをゼロにするとエラーにはなりませんが、なにも返って来ません

### 開始位置がゼロ及び正の整数、長さが負の整数の場合
```zsh
#開始位置が0及び正の整数、長さが負の整数
str=ABCDE; for i in {0..4}; for j in {$(($i-5))..-1}; do echo  '${"ABCDE":'$i':'$j'}='${str:$i:$j} ; done
#→ ${"ABCDE":0:-5}=
#→ ${"ABCDE":0:-4}=A
#→ ${"ABCDE":0:-3}=AB
#→ ${"ABCDE":0:-2}=ABC
#→ ${"ABCDE":0:-1}=ABCD
#→ ${"ABCDE":1:-4}=
#→ ${"ABCDE":1:-3}=B
#→ ${"ABCDE":1:-2}=BC
#→ ${"ABCDE":1:-1}=BCD
#→ ${"ABCDE":2:-3}=
#→ ${"ABCDE":2:-2}=C
#→ ${"ABCDE":2:-1}=CD
#→ ${"ABCDE":3:-2}=
#→ ${"ABCDE":3:-1}=D
#→ ${"ABCDE":4:-1}=
```
長さがマイナスの場合は、長さではなく取り出す最後の位置を、末尾からのオフセットで指定します
結果として最後の文字は取り出すことができません
わかりにくいですね

### 開始位置が負の整数、長さが負の整数の場合
```zsh
#開始位置が負の整数、長さが負の整数
str=ABCDE; for i in {-5..-1}; for j in {$(($i))..-1}; do echo  '${"ABCDE":'$i':'$j'}='${str:$i:$j} ; done
#→ ${"ABCDE":-5:-5}=
#→ ${"ABCDE":-5:-4}=A
#→ ${"ABCDE":-5:-3}=AB
#→ ${"ABCDE":-5:-2}=ABC
#→ ${"ABCDE":-5:-1}=ABCD
#→ ${"ABCDE":-4:-4}=
#→ ${"ABCDE":-4:-3}=B
#→ ${"ABCDE":-4:-2}=BC
#→ ${"ABCDE":-4:-1}=BCD
#→ ${"ABCDE":-3:-3}=
#→ ${"ABCDE":-3:-2}=C
#→ ${"ABCDE":-3:-1}=CD
#→ ${"ABCDE":-2:-2}=
#→ ${"ABCDE":-2:-1}=D
#→ ${"ABCDE":-1:-1}=
```
このパターンもわかりにくいですが、
開始位置も長さ（終了位置）も同じマイナスでの指定なので
そこまで難解なわけでもないかな

### まとめ
各項目のバリエーションと取りうる範囲として表にしました（が、解りにくいですね）

| 項目 | セット値 | 例1 | 例2 | 例3 | 例4 | 例5 |
|-|-|:-:|:-:|:-:|:-:|:-:|
| ①文字列|サンプル値<br>＝"ABCDE"|A|B|C|D|E|
| ②-1<br>開始位置|0及び正の整数<BR>（オフセット）|0|1|2|3|4|
| ②-2<br>開始位置|負の整数<BR>（末尾からの変位）|-4|-3|-2|-1|0|
| ③-1<br>長さ|0<br>（Nullを返却）|Null|Null|Null|Null|Null|
| ③-2<br>長さ|正の整数<BR>（取り出す文字数）|1〜5|1〜4|1〜3|1〜2|1|
| |結果|A〜ABCDE|B〜BCDE|C〜CDE|D〜DE|E|
| ③-3<br>長さ|負の整数<BR>（末尾からの変位）|-5〜-1|-4〜-1|-3〜-1|-4〜-1|-1|
| |結果|-5はNull<br>-4からはA〜ABCD|-4はNull<br>-3からはB〜BCD|-3はNull<br>-2からはC〜CD|-2はNull<br>-1はD|Null|
| ③-4<br>長さ|省略<BR>（最長文字数）|5|4|3|2|1|
| |結果|ABCDE|BCDE|CDE|DE|E|

<font color=red>
※「開始位置」は、ゼロ及び正の整数で先頭からのオフセット、負の整数で末尾からの変位となり、完全に互換性があります。

※「長さ」は、負の整数の場合、末尾からの変位となり、その位置の直前までしか取り出せません。そのため正の整数と負の整数で完全な互換性はありません（最後の文字が取り出せません）
※「長さ」は、ゼロだとNullがリターンされ、省略すると末尾までの文字列がリターンされます
</font>