初心者がシェルスクリプトを最低限理解したい

お勉強メモを整理します。
（個人的にまとめていたものなので一部出典が不明です、すみません、、）

# 基本知識
## そもそも
**シェル**：オペレーティングシステム（OS）と対話するためのインターフェース
→ コマンドなどを制御する環境

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/430585/1495e38a-b151-d538-cfd8-75e55245cfde.png)
(https://eng-entrance.com/linux-shellscript-what)

**シェルスクリプト**：シェルが解釈できるコマンドをまとめて実行できるようにドキュメントにしたもの（Unixコマンドなど）
→ Linuxのターミナル操作に通ずるものがある？

・「いつ」「何の条件で」「何の命令をする」
・ファイルコンテンツを読み込む
・ログファイルを書き出す
などの制御が可能

☆ シェルスクリプトは**インタプリタ**
記述内容の上から順に、【コマンド → 制御文 → 変数など】を解釈しながら実行する
＝コンパイルがないのですぐに実行結果がわかる

## お約束
・拡張子：``.sh``

・最初の1行目：``#!/bin/sh``（シェバング、シバン）
システムに「この下はシェルスクリプトを書いています」と知らせる
※ bash独自の機能を使う場合は``#!/bin/bash``と書く

・文末に``;``（セミコロン）はいらない

```sh:example.sh
#!/bin/sh

echo "Hello, World!"
```
対象のシェルスクリプトファイルは``sh（またはbash） ファイル名``で実行

```terminal:terminal（結果）
$ sh example.sh

$ Hello, World!
```

# 基本処理
### 1. コメント
```sh
#!/bin/sh

# コメント
# 実行されない
```

### 2. 入力・出力
```sh
read NAME  # 入力
echo "Hello, ${NAME}!"  # 出力
```
```console:結果
Taro
Hello, Taro!
```

### 3. 変数
＜条件＞
・ 使える文字・・・a～z(小文字)、A～Z(大文字)、0～9(数字)、_(アンダースコア)
・ 先頭文字に数字は不可
・ ``=``の前後には空白なし
・ 変数のアクセスは単体の場合``$変数``、文字の連結などがある場合``${変数}``
→ ``${変数}``を使用しておけば間違いない？

```sh
var="これは変数です"
Var_2="これも変数"
echo "Var_2=${Var_2}"  # 結果：「Var_2=これも変数」

# 変数を上書き不可
readonly var  # 上書きするとエラー

# 変数削除（readonly変数は不可）
unset Var_2
```

#### ☆ 特殊な変数
| 変数 | 内容 | 結果例 |
|:--:|:--|:--|
| $0 | スクリプト名 | echo $0 → script.sh |
| \$1 ~ $9 | シェル引数 |  |
| $# | シェル引数の数 | echo $# → 3（引数3つ） |
| $* | 全引数のリスト | echo "$*" → "first second third" |
| $@ | 全引数のリスト（個別） | echo "$@" → "first" "second" "third" |
| $- | シェルの実行オプション（setコマンドや#!/bin/bash行に付けたオプション） |  |
| $? | 	最後に実行したコマンドの終了値(戻り値) | echo $? → 0（成功）もしくは1（失敗） |
| $$ | 実行中のシェルのプロセスID |  |
| $! | シェルが最後に実行したバックグラウンドプロセスID |  |

#### ☆ 変数値の置換
| 方法 |  |
|:--:|:--|
| ${var} | 変数へのアクセス |
| ${var:-word} | 中身チェック：空文字の場合*word*を返す |
| ${var:=word} | 中身チェック：空文字の場合*word*を返す<br>**varに文字列を保存する** |
| ${var:?word} | 中身チェック：空文字の場合**エラー** |
| ${var:+word} | 中身チェック：**空文字ではなかった場合***word*を返す |

```sh:example.sh
echo "1 - ${var:-wordSetInEcho1}"
echo "2 - var = ${var}"
echo "3 - ${var:=wordSetInEcho3}"
echo "4 - var = ${var}"
unset var
echo "5 - ${var:+wordSetInEcho5}"
echo "6 - var = $var"
var="newVarValue"
echo "7 - ${var:+wordSetInEcho7}"
echo "8 - var = $var"
echo "9 - ${var:?StandardErrorMesage}"
echo "10 - var = ${var}"
```
```:（結果）
1 - wordSetInEcho1            # varは空なのでword~がそのまま返ってくる
2 - var =                     # varに保存されないため空のまま
3 - woedSetInEcho3 　　　　　　# varは空なのでword~がそのまま返ってくる
4 - var = wordSetInEcho3      # word~がvarに保存されている
(unset varにより変数削除)
5 -                           # varは空なのでword~が返ってこない
6 - var =                     # varに保存されないため空のまま
(var代入)
7 - wordSetInEcho7            # varにnew~が入っているためword~が返ってくる
8 - var = newVarValue         # varに入っているnew~が出力される
9 - newVarValue               # varにnew~が入っているため成功、そのまま出力される
10 - var = newVarValue        # varに入っているnew~が出力される
```

### 4. 配列（Bash）
```sh
ARRAY=(item1 item2 item3 item4)
ARRAY[0]="ITEM1"
ARRAY[2]="ITEM3"

echo "ARRAY[0]: ${ARRAY[0]}"
echo "ARRAY[*]: ${ARRAY[*]}"
```
```console:結果
ARRAY[0]: ITEM1
ARRAY[*]: ITEM1 item2 ITEM3 item4
```

### 5. オペレータ
算術演算子：\`expr 数字 演算子 数字`

| 演算子 | 例 |
|:--:|:--|
| + | echo \`expr 10 + 20` => 30 |
| - | echo \`expr 20 - 10` =>10 |
| ＼* | echo \`expr 11 ＼* 11` => 121 |
| / | echo \`expr 10 / 2` => 5 |
| % | echo \`expr 10 % 4` => 2 |
| = | a=$b (bの値はaに保存される) |
| == | [ "\$a" == "\$b ] \(\$aと$bが同じ場合**TRUE**を返す) | 
| != | [ "\$a" != "\$b ] \(\$aと$bが同じでない場合**TRUE**を返す) |

比較（以下の時TRUE)

| 演算子 | 内容 | 意味 |
|:--:|:--|:--|
| -eq | [ "\$a" -eq "\$b" ] \$a = \$b | equal to |
| -ne | [ "\$a" -ne "\$b" ] \$a ≠ \$b | not equal to |
| -gt | [ "\$a" -gt "\$b" ] \$a > \$b | greater than |
| -lt | [ "\$a" -lt "\$b" ] \$a < \$b | less than |
| -ge | [ "\$a" -ge "\$b" ] \$a >= \$b | greater than or equal to |
| -le | [ "\$a" -le "\$b" ] \$a <= \$b | less than or equal to |
| ! | [ ! "\$a" -gt "\$b" ] !(\$a > \$b) | | 
| -o | [ "\$a" -gt "\$b" -o "\$a" -lt "\$b" ] | or |
| -a | \[ "\$a" -gt 90 -a "\$a" -lt 100 ]  | and | 
| -z | [ -z "\$a" ] \$aが何も指定していない | |
| -n | [ -n "\$a" ] \$aが何かを指定している | |

### 6. if条件
`if [ 条件 ] then コマンド fi`

```sh:example.sh
if [ "$1" -gt "$2" ]
then
     echo "1番目のが2番目より大きい"
elif [ "$1" -eq "$2" ]
then
     echo "1番目と2番目は同じ"
else
     echo "1番目のが2番目より小さい"
fi
```
```console:結果
$ ./example.sh 2 7
1番目のが2番目より小さい
$ ./example.sh 10 5
1番目の引数が2番目の引数より大きい
$ ./example.sh 9 9
1番目の引数と2番目の引数は同じです
```

### 7.switch条件
`case 変数 in 条件・値) コマンド ;; esac`

```sh:example.sh
DRINK="coffee"
case "$DRINK" in
     "beer") echo "ビールです"
     ;;
     "juice") echo "ジュースです"
     ;;
     "coffee") echo "コーヒーです"
     ;;
esac
```
```console:実行結果
 コーヒーです
```

### 8. ループ
・`while`：条件が合うときにループする

```sh
a=0
while [ $a -lt 5 ]
do
     echo $a
     a=`expr $a + 1`
done
```
```console:実行結果
 0
 1
 2
 3
 4
```

・`until`：条件が合う**まで**ループする（whileの逆、条件があったら終了）

```sh
a=0
until [ ! $a -lt 5 ]
do
     echo $a
     a=`expr $a + 1`
done
```
```console:実行結果
0
1
2
3
4
```

・`for`：条件・値が変数と合う場合、コマンドを実行する
`for 変数 in 複数値・変数・範囲 do コマンド done`

```sh
for var in 0 1 2 3 4 
do
     echo $var
done
```
```console:実行結果
0
1
2
3
4
```

（共通ルール）
`break`：ループ終了
`continue`：ループスキップ

### 9. 関数
```sh
#関数の指定
MyFunction() {
     echo "関数のechoです"
}
MyParamFunc() {
     echo "引数1：$1 引数2：$2"
}

#関数の呼び出し
 MyFunction
 MyParamFunc param1 param2
```
```console:実行結果
関数のechoです
引数1：param1 引数2：param2
```

### 10. ファイルチェック
条件として使う

| オプション | 内容 |
|:--|:--|
| -a ファイル名 | ファイルが存在する |
| -d ファイル名 | ファイル名がディレクトリである |
| -e ファイル名 | ファイルが存在する |
| -f ファイル名 | ファイルが存在し、且つ、通常のファイル |
| -L ファイル名 | ファイルが存在し、且つ、シンボリックリンク |
| -r ファイル名 | ファイルが存在し、読み取り可能 |
| -s ファイル名 | ファイルが存在し、空ではない |
| -S ファイル名 | ファイルがソケットである |
| -w ファイル名 | ファイルが存在し、書き込み可能
| -x ファイル名 | ファイルが存在し、実行可能（ディレクトリ→検索可能） |
| ファイルA -nt ファイルB | ファイルAがファイルBより新しい |
| ファイルA -ot ファイルB | ファイルAがファイルBより古い |

```sh
if [ ! -f /var/log/example.log ]; then
    echo "file not found"
else
 : # 何も処理を行わない
fi
```
/var/log/example.logがあればスルー
/var/log/example.logがなければ処理実行

## 発展

### 11. ファイル行数、ファイル名取得
`wc -l`

☆ 行数のみ取りたい場合

```sh
$ cat sample.txt | wc -l 
$ wc -l sample.txt | cut -d " " -f 1
$ wc -l sample.txt | awk '{print $1}'
```

### 12. 文字列処理
`sed -e`（Stream Editor）
処理内容は、置換処理や行の削除、選択などいろいろできて、オプション -e で指定する

・文字列置換
`sed -e 's/変更前の文字列/変更後の文字列/g' ./data.txt > ./data-new.txt`

・文字列の一括削除
`sed -e 's/変更前の文字列//g' ./data.txt > ./data-new.txt`

・2～4行目を削除
`sed -e ‘2,4d’ data.txt> ./data-new.txt`

### 13. ファイルの最終更新日を変更
`touch ファイル名`
“ファイルをいま保存した”ようにタイムスタンプを変更する







# 参考
1. [【1分でわかる】シェルスクリプトとは？ | エンジニアの入り口](https://eng-entrance.com/linux-shellscript-what)
2. [特殊変数:引数などの特殊な変数 | Technical tips:Media hub](http://tech.clickyourstyle.com/articles/180)
3. [sed コマンド | hydroculのメモ](https://hydrocul.github.io/wiki/commands/sed.html)
4. [【 touch 】コマンド――タイムスタンプを変更する／新規ファイルを作成する：Linux基本コマンドTips（23） | ＠IT](https://www.atmarkit.co.jp/ait/articles/1606/14/news013.html)