Bashスクリプトの雑多なまとめ (my備忘録)

[my備忘録 シリーズ](#my備忘録一覧) 2つ目。

自分がスクリプトを書く際に「あの処理って、どう記述するんだっけ？」とあれこれ検索し直すことが度々あったので。作業効率化のために、一箇所にまとめてゆく予定。(新しいことを学ぶ度に随時追加)
- Linux標準(+α)のコマンドについても、簡単にまとめる。
- 複数コマンドの組み合わせ技や、(自分がやりたかった)極めて限定的な目的に対する(自分なりに見出した)解決策も載せる。

※応用範囲が狭いこと、当方の技量不足により必ずしも最適解とは限らないことにご留意ください。
※実際にコマンドを実行し、おかしな動作にならないか確認はしていますが、実行環境の違いや記載ミス等の理由により問題が発生する可能性がありますので、十分ご注意ください。

<!-- =========================================================== -->
# 目次
<!--[目次へ](#目次)-->
***I. 変数***
[- **<ins>1. 特殊変数</ins>**](#1-特殊変数)
[- **<ins>3. 変数の展開と結合</ins>**](#3-変数の展開と結合)

***II. 制御構文***
[- **<ins>1. for ループ</ins>**](#1-for-ループ)
[- **<ins>2. if 構文</ins>**](#2-if-構文)

***III. 入出力***

***IV. 応用事例***

***XXX.便利なコマンドいろいろ***
[- <ins>echo</ins>](#echoコマンド),　[<ins>printf</ins>](#printfコマンド)


[<b><ins>参考文献</ins></b>](#参考文献)




<!-- =========================================================== -->
# I. 変数
- 変数の宣言は基本的には必要ない。
- 変数への代入は`変数名=設定したい値`で行う。注意点として、代入演算子`=`の前後には**スペースを空けずに** `str='hoge'`, `var=3.14`のように記述する必要がある。
- 変数の値を参照する際には`$`を頭につけて`$変数名`で呼び出す。例：`echo $var`
- `declare`コマンドを用いると、変数のタイプを指定した上で宣言することもできる。

<!-- ----------------------------------------------------------- -->
## 1. 特殊変数

|変数|説明|
|:-|:-|
|`$?` | 直前に実行されたコマンドの終了ステータス (数値)   |
|`$0` | 現在実行されているコマンドの名前 | 
|`$$` | 現在実行されているコマンドのプロセスID |
|`$#` | 現在のスクリプトに渡された引数の個数 |
|`$@` | 現在のスクリプトに渡された全引数 (文字列) |
|`$1`, `$2`, ...<br>, `${10}`, ...| 現在のスクリプトに渡された`(数値)`番目の引数 |
```bash:実行例 (sample_args.sh)
echo "name='$0'"
echo "Nargs="$#
for i in $(seq $#); do
    echo -n "arg_num=$i, "
    eval "echo arg=\'\$${i}\'"
done
echo "args='$@'"
for arg in "$@"; do echo "arg='${arg}'"; done
```
<details><summary> <ins>> bash sample_args.sh 2 b</ins> と実行した結果</summary>

:::note
name='./sample_args.sh'
Nargs=2
arg_num=1, arg='2'
arg_num=2, arg='b'
args='2 b'
arg='2'
arg='b'
:::
</details>

- `$@`と`$*`の違い：

- より詳しい情報は[<ins>こちら</ins>][bash_variables]を参照。

<!-- ----------------------------------------------------------- -->
## 2. 配列 

[↩[back]](#目次)
<!-- ----------------------------------------------------------- -->
## 3. 変数の展開と結合
<!-- ----------------------------------------------------------- -->
### 変数展開 (parameter expansion)
- この機能を使うことで、変数文字列に対してさまざまな操作をすることができる。以下は、その一例。

```bash:文字列の置換
str="fuga_fuwa_fuga" #対象文字列

#パターンマッチ部分の置換
echo ${str/a/o} #aをoに置換。結果: fugo_fuwa_fuga

#全てのパターンマッチ部分の置換
echo ${str//a/o} #aをoに置換。結果: fugo_fuwo_fugo

#先頭からマッチした部分の置換
echo ${str/#fuga/puwa} #結果: puwa_fuwa_fuga

#末尾からマッチした部分の置換
echo ${str/%fuga/puwa} #結果: fuga_fuwa_puwa
```

```bash:文字列の削除
str="hoge_fuga_fuwa_fuga" #対象文字列

#前方最短一致削除(#以降の文字列パターンについて、前方最短一致する部分までを除去)
echo $(str#ho}  #実行結果: ge_fuga_fuwa_fuga
echo $(str#*f}  #実行結果: uga_fuwa_fuga

#前方最長一致削除(##以降の文字列パターンについて、前方最長一致する部分までを除去)
echo $(str##*f}  #実行結果: uga

#後方最短一致削除 (%以降の文字列について、後ろから探索して最短一致パターンを除去)
echo ${str%_fuga*} #実行結果: hoge_fuga_fuwa

#後方最長一致削除: (%%以降の文字列について、後方から最長一致パターンを除去)
echo ${str%_fuga*} #実行結果: hoge

#置換構文による部分削除 (置換後文字列を空白にすることで削除を実現できる)
echo ${str%//_fuga/} #実行結果: hoge_fuwa

#置換構文による部分削除 (置換後文字列とのセパレーター'/'はなくても大丈夫っぽい)
echo ${str%/_fuwa} #実行結果: hoge_fuga_fuga

```
[↩[back]](#目次)
<!-- ----------------------------------------------------------- -->
## 4. コマンドの実行結果を変数に代入する


<!-- =========================================================== -->
# II. 制御構文
<!-- ----------------------------------------------------------- -->
## 1. for ループ 
ループ変数は数値だけではなく、任意の文字列が指定できる。たとえば、ファイル名のリストに対して使うこともできるので便利。
```bash:基本
#in の後に配置された文字列について、1つずつ取り出し、順番に処理を実行する
for i in {0..2}　
do #"0 1 2"という文字列に対してループさせる
    echo $i #なんらかのループ中に実行したい処理
done
```
```bash:ワンライナー (上と同じ処理)
for i in {0..2}; do echo $i; done
```
:::note
実行結果:
0
1
2
:::

```bash:文字列に対するループ処理の例
for i in a{b..d}e; do echo $i; done
```
:::note
実行結果:
abe
ace
ade
:::

```bash:整数ループに対する、イテレータ部分の書き方いろいろ
for i in {0..2}; #「ブレース展開」により、数列"0 1 2"を生成
for i in `seq 0 2`; #seq コマンドを利用し、数列を生成。`command`により実行結果を展開
for i in $(seq 0 2); #同上。「コマンド置換」 $(commnad)により実行結果を展開
for ((i=0; i<3; i++)); #「初期値,ループ条件,次ループ時の処理」を指定するおなじみの形式
for ((i=0; i<=2; i++)); #同上
```
- `ブレース展開` ： ある規則にしたがって連続した文字列を生成する機能。([参考ページ][brace_expansion_qiita])

### 複数ファイルに対してループ処理を実行する
```bash:ディレクトリ内にある特定ファイル群に対して実行
for file in *;        #カレントディレクトリを指定 (最も簡潔)
for file in ./*;      #カレントであることを明示

for file in "$dir"/*;        #ディレクトリを(変数によって)指定
for file in "$dir"/*.txt;    #ファイル拡張子も指定 (.txtファイルについてループ)

for file in $(ls "$dir"/*.txt);      #コマンドの実行結果を利用
for file in $(find "$dir" -type d);  #コマンドを利用 (ディレクトリファイルについてループ)
```
[↩[back]](#目次)

<!-- ----------------------------------------------------------- -->
## 2. if 構文

- 基本構文
- `[ ]`と`[[ ]]`の違い

### 条件の指定
```bash
#or 
#and
#複数組み合わせ
```
### 文字列の比較

### 数値の比較

### ファイルの有無の確認
```bash: ファイル ./hoge について
if [[ -e ./hoge ]];    #ファイル'./hoge'が存在すれば真 ('e'xist)
if [[ ! -e ./hoge ]];  #ファイル'./hoge'が存在しなければ真 (Not 'e'xist)

if [[ -f ./hoge ]];  #通常ファイルが存在するか ('f'ile)
if [[ -d ./hoge ]];  #ディレクトリが存在するか ('d'irectory)
if [[ -L ./hoge ]];  #シンボリックリンクが存在するか ('L'ink)

if [[ -s ./hoge ]];  #空でないファイルが存在するか
#-> ファイルが存在し、かつファイルサイズが非ゼロならば真 ('s'ize)
```

### その他
#### 恒真, 恒偽条件による制御
```bash
if true;  then $(command); fi  #常に実行される
if false; then $(command); fi  #常に実行されない
```
スクリプトのある範囲について、実行する/しないを制御できるので、デバッグなど一時的な変更に便利


[↩[back]](#目次)

<!-- =========================================================== -->
# 便利なコマンドいろいろ
<!-- ----------------------------------------------------------- -->
### echoコマンド
```bash
echo 文字列
```
やまびこ(echo)のように、入力された文字列をそのまま返す(出力する)。
<!--<details><summary> ちょっとくわしい説明 </summary>-->
<details><summary> ちょっとくわしい説明 </summary>

|オプション| 説明 |
|:-:| -|
|-n | 出力文字列の最後に改行しない |
|-e | `エスケープシーケンス`を解釈する |
```bash:使用例
echo "test"; echo 10 #基本
# test
# 10
str="tes"; echo $str #変数を出力
# tes
echo "test" 10 $str #複数出力
# test 10 tes
echo '$str' "$str" #クオーテーションによる違い(シングル内は文字列扱い、ダブル内は変数展開)
# $str tes
echo -n " a"; echo b #末尾の改行なし
# a b
echo -e " a\n"; echo b #改行コードの使用
# a
#
# b
echo $((3*5)); echo $(printf "%03d" 7) #コマンドの実行結果を出力
# 15
# 007
```
</details>

<!-- ----------------------------------------------------------- -->

### printfコマンド
```bash
printf フォーマット指定 入力値1 入力値2 ...
```
フォーマット(format)を指定して入力された値(文字列や変数)を出力(print)する。
<!--<details><summary> ちょっとくわしい説明 </summary>-->

- `フォーマット指定子`は`%e`, `%f`, `%g`, `%d`, `%s`, `%c`などおなじみ。C言語などと同じ。
```bash:使用例
printf %e'\n' 0.1 #末尾は改行されないので、標準出力の際には改行コードを加える
# 1.000000e-01

# いろいろ出力
printf "%5s %02d; root2 =%.4f, Napier =%g\n" \
       "res" 1 \
       $(awk 'BEGIN{print sqrt(2)}') \
       $(echo "scale=10; e(1)" | bc -l)
#   res 01; root2 =1.4142, Napier =2.71828

`コマンド` | printf 書式 `cat`
（コマンドの実行結果をprintfで整形して表示する）
```
</details>

<!-- ----------------------------------------------------------- -->

### timeコマンド
```bash
time 何らかのコマンド
```
実行時間を計測したいコマンドの前にtimeを置けば、結果を出力してくれる。
<!-- =========================================================== -->
# 参考文献
### 全般的に
- [新しいシェルプログラミングの教科書, 三宅 英明, SBクリエイティブ, 2017](https://bookwalker.jp/de1b9c0e68-62af-4eb8-853d-7eadffd9099f/)

### 変数について
- [変数を使用する - UNIX & Linux コマンド・シェルスクリプト リファレンス][bash_variables]
- [bashのブレース展開についてまとめ - Qiita][brace_expansion_qiita]

[bash_variables]: https://shellscript.sunone.me/variable.html
[brace_expansion_qiita]: https://qiita.com/ine1127/items/6e5fe80f4a9c64509558

### 制御構文について

- [bashのfor文のまとめ](https://www.koikikukan.com/archives/2014/03/03-015555.php)

## my備忘録一覧
1. [Qiitaで使うMarkdown記法 myまとめ (備忘録)](https://qiita.com/orioriz22/items/c4e452a9ff245852f616)
1. Bashスクリプトの雑多なまとめ (my備忘録) ← 本記事