【Linux】シェルスクリプトについて学んだことを書く


## はじめに
シェルスクリプトについて、学んだことを備忘録としてまとめる。

### shにするかbashにするか
* shの特徴
    * 長い歴史を持つ反面，現在では設計が古い
    * 過去のシェルスクリプトの多くがsh向けに書かれている
    * Debian系ディストリビューションではshをdashとシェルで実装
    * Red Hat系ディストリビューションはshをbashの別名として実装(shとbashに実体はRed Hat系では同じ)
* bashの特徴
    * shよりもシェルスクリプトを書く上で便利な機能が用意されている
    * 現代のLinuxでは，bashはほぼデフォルトでインストールされている

#### shよりもbashを検討したほうがいい
理由

* シェルスクリプトの互換性・移植性が高い

  →shはすべてのLinuxに存在するが，OSによって実装の差異がある

* 便利な機能が用意されている

→算術計算する際，shでは外部コマンドexprを使用する必要があるが，bashで外部コマンドなしに計算可能(シェル内部の機能なため，動作も高速）

### シェルスクリプトの実行形
ファイルの先頭に`#!`で始まる行を追加する必要があるが,これは**shebang(シバン，シェバン)**と呼ばれるもの．
Linuxカーネルが`#!`を確認すると，その後ろのコマンドを実行する．
`#!/bin/bash`は「このシェルスクリプトは/bin/bashで動かす」という意味.
シバンを書くことにより，`/bin/bash`をコマンドラインに書かなくても，ファイル名を指定することにより，シェルスクリプトを実行できる

```alias_set.sh
#!/bin/bash
alias lsalf='ls -alf'

```


```console
$ lsalf #エイリアス未設定
lsalf: コマンドが見つかりません

$ source ./alias_set.sh #sourceコマンドでエイリアス設定
$ lsalf
arg.sh         if-bin.sh  getdate.sh    homesize.sh  tes1.sh  .
parameters.sh  rootls.sh  alias_set.sh  2019-10-02   ..

$ lsalfsh #エイリアスは未設定
lsalfsh: コマンドが見つかりません
$ cat alias_set.sh  #lsalfの名前をlsalfshに変更後，catで内容表示
#!/bin/bash
alias lsalfsh='ls -lf'　#lsalfshに変更

$ ./alias_set.sh　#シェルスクリプトを実行
$ lsalfsh        #シェルスクリプト終了後もエイリアスは未設定
lsalfsh: コマンドが見つかりません

```

これはなぜ起こるのか．
まずsourceコマンドでは，カレントシェルでシェルスクリプトが実行されるため，現在の環境が引き継がれる．
それに対し，ファイル名で実行した場合には，サブシェルでシェルスクリプトが実行されるため，元のシェルとは別物であり，エイリアスの設定は引き継がれない．(環境変数は引き継がれる)
サブシェルとは，現在のシェルから新しく起動される子プロセスのシェルのこと．

```console
$ ./rootls.sh 
root directory
合計 483908
drwxr-xr-x   2 root root      4096  9月 30 11:54 bin
drwxr-xr-x   3 root root      4096  9月 30 11:56 boot
drwxrwxr-x   2 root root      4096  9月 30 00:24 cdrom
drwxr-xr-x  18 root root      4020  9月 30 21:54 dev
略
```

### 変数名の参照
変数の値を参照するには，変数名の前に`$`を付ける
変数の書き方の注意

* 代入時には$を付けない
* =の前後にはスペースを入れない
* 変数名に利用できる文字はアルファベットと数値とアンスコのみ
* {}を使用して変数名の区切りを明示する
 * ex) 変数名(filename)＋任意の文字列(str)を連結して標準出力する場合
     * ダメな例⇒echo filename_str
     * 正しい例⇒echo {filename}_str

### クウォーティング
クウォーティングで囲まれた文字列は一つの単語として認識される
`''`と`""`の違いについて
変数展開が有効かどうか
シングルクォート:`$`による変数の展開が行われない
ダブルクォート：`$`による変数の展開が行われる

### コマンド置換

```getdate.sh
#!/bin/bash

filename=$(date '+%Y-%m-%d')
touch "$filename"
```


```
$ ./getdate.sh
$ ls -l
合計 16
-rw-r--r-- 1 gyokoyama gyokoyama  0 10月  2 08:00 2019-10-02
-rwxr-xr-x 1 gyokoyama gyokoyama 60 10月  2 07:59 getdate.sh
-rwxr-xr-x 1 gyokoyama gyokoyama 32 10月  1 08:03 homesize.sh
-rwxr-xr-x 1 gyokoyama gyokoyama 50 10月  1 09:01 rootls.sh
-rwxr-xr-x 1 gyokoyama gyokoyama 20 10月  1 08:15 tes1.sh
```

2019-10-02というファイル名が追加されている.

### 位置パラメータ

```parameters.sh
#!/bin/bash

echo "\$0 = $0"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$4 = $4"
echo "\$5 = $5"
```

```
$ ./parameters.sh aaa bb ccccc
$0 = ./parameters.sh
$1 = aaa
$2 = bb
$3 = ccccc
$4 = 
$5 = 
```
#### ワイルドカードの使用
`*`:任意の文字列の代わりになる．文字列の長さは0でもよい．
`?`:任意の1文字の代わりになる．
`[]`:かぎ括弧の中に記述した任意の1文字の代わりになる。連続する文字コードの指定には - 記号が使える。たとえば， [a-c] は [abc] と同じ意味．

```
$ ls
2019-10-02  getdate.sh  homesize.sh  parameters.sh  rootls.sh  tes1.sh
$ ./parameters.sh *
$0 = ./parameters.sh
$1 = 2019-10-02
$2 = getdate.sh
$3 = homesize.sh
$4 = parameters.sh
$5 = rootls.sh
```

#### 引数の個数
コマンドライン引数に指定された引数の個数は`$#`という変数で参照できる．
最後の行に`echo "\$# = $#"`を追記.

```paraters.sh
#!/bin/bash

echo "\$0 = $0"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$4 = $4"
echo "\$5 = $5"
echo "\$# = $#"
```


```
$ ./parameters.sh *
$0 = ./parameters.sh
$1 = 2019-10-02
$2 = getdate.sh
$3 = homesize.sh
$4 = parameters.sh
$5 = rootls.sh
$# = 6
```

```
$./parameters.sh aaa bb c
$0 = ./parameters.sh
$1 = aaa
$2 = bb
$3 = c
$4 = 
$5 = 
$# = 3
```

#### 引数全体の参照
`$@`，`$*`を使用して，引数を分割せず全て一括りのようにして参照する．

```arg.sh
#!/bin/bash

echo "\$@ = $@"
echo "\$* = $*"
```

```
$ ./arg.sh aa bbbb cc
$@ = aa bbbb cc
$* = aa bbbb cc
```

* `$@`，`$*`の違い
    * `$@`は位置パラメータがそれぞれ1つずつの文字列として展開される(上記の例では，'aa' 'bbbb' 'cc'として展開)
    * `$*`は引数全体が1つの文字列として展開されている(上記の例では，'aa bbbb cc'として展開)

### if文の制御構造

```if-bin.sh
#!/bin/bash

if [ "$1" = "bin" ]; then
	echo "ok"
else
	echo "NG"
fi
```

```
$ ./if-bin.sh bin
ok
$ ./if-bin.sh bash
NG
```
#### 記述の注意点
* `if [ "$1" = "bin" ] then `
⇒条件を書いた後に`;`を省略するとエラー(`then`を改行すればセミコロン不要)

* `if ["$1" = "bin"] `
⇒`[]`の前後に半角スペースがないとエラー

#### if文の仕組み

シェルスクリプトでは，if文の後ろに置くのは「コマンド」であって，条件式ではない．
`[`は単なる括弧ではなく，bashの組み込みコマンド．
`[`コマンドは引数に書かれた条件式を判定し，その条件が正しければ0，そうでなければ0以外を返すコマンド．if文では0か0以外かどうかで真偽を判定
if直後の文は終了ステータスの値で真偽を判定するため，`[`コマンド以外でも可能．[評価演算子等](https://qiita.com/egawa_kun/items/196cd354c0d8e4e0fefc)を組み合わせて活用する．



#### 終了ステータス
終了ステータスとはどういうものか．
lsやgrepコマンドなどは，終了時にある整数値を返す．その整数値のことを終了ステータスという．コマンドが正常に終了した場合は0，エラー時は0以外を返す．
`exit status = 0`，`exit status = 1`とかの整数値が終了ステータスにあたる．