シェルスクリプトの基本的な文法と演習

## はじめに
 シェルスクリプトにまつわる記事を投稿し始めて、早くも３本目。これまでは実際に.shファイルを作成するまでの基礎的な部分を説明してきたため、実際にファイルを作って動かしてみることはやってきませんでした。
　しかし改めて.shファイルを記述して、エラーを出して、なんとか動かしてみるとやっぱりこれが一番楽しいですね。エンジニアになって良かった。地球に生まれて良かったとすら思えます。
前回までの記事:

https://qiita.com/KentaroYoshizumi/items/acec0cf38e8dcd697731


https://qiita.com/KentaroYoshizumi/items/601e5bd4f0a1e391788e

　具体的に行った手順をメモにして残して、読んでいただけた方には実際に手を動かしてシェルスクリプトを書いて欲しいですね。それが上達にもつながる気がしています。
　通して読んでくださっている方には本当に感謝です。それでは文法から、見ていきましょう！

## シェルスクリプトの基本
　シェルスクリプトは独特の文法構造を持っており、やはり書いていかないことには身につかないと思います。（変数の定義にスペースを使わない。全角スペースが入っていても空行は無視される。等）
基本的な文法を下記のようにまとめておくので、ぜひ手を動かして書いてみて欲しいです。
全て私が実践して、実際に動いたスクリプトを掲載しています。動かせる環境を作れることも重要なのでご自身でやってみてください。それでは行きましょう。

### シェルスクリプトの行
**・\で改行できる。(\はoption+¥キーで入力できる)**

```dinner.sh
#!/bin/bash -eu

echo \　（変なところで改行している。）
"Today's dinner."
```

```実行結果
mbp:shellscripts kentaroyoshizumi$ . dinner.sh (カレントディレクトリでdinner.shを実行せよという意味)
Today's dinner.
```

### コメント
**・#の直後はコメントになる。
```dinner.sh
#!/bin/bash -eu

echo \
"Today's dinner."  #today is my birthday.
```
### 変数
シェルスクリプトで使用する変数を「シェル変数」と呼ぶ。<font color="red">**シェル変数を定義するには、変数名の前に「$」をつける。**</font>
ただし注意もあり、
- 変数を定義する際に$をつけてしまうとエラーになる。
- 変数を定義する際に=の前後にスペースを入れてしまうとエラーになる。(birthday = 1021 等)
- 変数名に利用できるのはアルファベットと数字と_のみ。
- 変数名に_を使用する際には{}で括らなければならない。(後述)

```birthday.sh
#!/bin/bash -eu

bitrthday=1021
echo $birthday
```

実行結果
```
mbp:shellscripts kentaroyoshizumi$ . birthday.sh
1021
```

**変数名に_を使用する際には{}で括らなければならない。**
```birthday1.sh
#!/bin/bash -eu

bitrthday=1021
echo $birthday_today_is_my_birthday
```
実行結果
```
mbp:shellscripts kentaroyoshizumi$ . birthday.sh
（何も表示されない）
```
```birthday1.sh
#!/bin/bash -eu

bitrthday=1021
echo ${birthday}_todayismybirthday
```
実行結果
```
mbp:shellscripts kentaroyoshizumi$ . birthday.sh
1021_todayismybirthday
```

### クォーティング
シングルクォートとダブルクォートで実行結果が異なる。

```quote.sh
#!/bin/bash -eu
country=Japan
echo 'I came from $country'
echo "I came from $country"
```
実行結果
```
I came from $country
I came from Japan
```

### コマンド置換

コマンドの結果を文字列として取得する。

```date.sh
#!/bin/bash -eu

filename=$(date '+%Y-%m-%d')
echo "$filename"
```
実行結果
```
2022-03-27
```

### 位置パラメータ
<font color="red">**めっちゃ使います！**</font>
```
$ echo $?
0
```
直前に使用したコマンドがうまく行ったかどうかを示す。(0なら成功、1は失敗)

位置パラメータのテスト用シェルスクリプト
```param.sh
#!/bin/bash -eu

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

コマンドラインから以下を実行
```
$ ./param.sh aaa ggg ttt
```

出力結果
```
$0 = ./param.sh
$1 = aaa
$2 = ggg
$3 = ttt
```

### 制御構造

**bashは他のプログラミング言語同様条件分岐や繰り返し処理を行うことができる。**

#### if文
```if-bin.sh
#!/bin/bash -eu
if [ "$1" = "bin" ]; then
 echo "OK"
else
 echo "NG"
fi
```
#### for文
**単語のリストに対して繰り返し処理を行う**

```forexample.sh
#!/bin/bash -eu

for name in aaa ccc ggg
do
 echo $name
done
```

実行結果
```
aaa
ccc
ggg
```
#### case文

**指定された文字列がパターンにマッチするか判定し、パターンごとに対応した処理を行う。**
```case-test.sh
#!/bin/bash -eu

case "$1" in
*.txt)
less "$1"
;;
*.sh)
vim "$1"
;;
*)
echo "not supported file : $1"
;;
esac
```
実行結果
```
mbp:shellscripts kentaroyoshizumi$ . case-test.sh /Users/kentaroyoshizumi/Downloads/RubyProject
not supported file : /Users/kentaroyoshizumi/Downloads/RubyProject
```
#### while文
**指定した条件が真である限りくりかえし処理を行う。**

```while.sh
#!/bin/bash -eu

i=1
while [ "$i" -le 10 ]
do
 echo "$i"
 i=$((i + 2))
done
```
実行結果
```
mbp:shellscripts kentaroyoshizumi$ . while.sh
1
3
5
7
9
```
### シェル関数
**シェルスクリプト内で関数を定義する**

```func-test.sh
#!/bin/bash -eu

horizonte ()
{
date
du -h ~| tail -n 1
}

horizonte
```

実行結果

```
mbp:shellscripts kentaroyoshizumi$ . func-test.sh
2022年 4月 2日 土曜日 16時58分49秒 JST
```
#### シェル関数内での位置パラメータ

**シェルスクリプトの中で位置パラメータを使用する。**

```func-parameter.sh
#!/bin/bash -eu

echo_parameter ()
{
 echo "\$1 = $1"
 echo "\$2 = $2"
 echo "\$3 = $3"
 echo
 echo "$# arguments"
 echo "scripts name= $0"
}

echo_parameter aaa bbb ccc
```

### シェルスクリプトの活用

#### 演習1-1 指定されたファイルやディレクトリの概要を表示するシェルスクリプト
```summarize.sh
#!/bin/bash -eu

file=$1

if [ -d "$file" ]; then
  echo "$file is a directory:"
  ls "$file"
elif [ -f "$file" ]; then
  echo "$file is a regular file:"
  head -n 5 "$file"
fi
```
実行結果
```
shellscripts is a directory:
birthday.sh	date.sh		if-bin.sh	summarize.sh
daily.sh	dinner.sh	param.sh
```
#### 演習1-2 前問の複数ファイルを指定したもの
```summarize1.sh
#!/bin/bash -eu

for file in "$@"
do
  if [ -d "$file" ]; then
    echo "$file is a directory:"
    ls "$file"
  elif [ -f "$file" ]; then
    echo "$file is a regular file:"
    head -n 5 "$file"
  fi
done
```
実行結果

```
RubyProject is a directory:
2:15_S3_or_not.drawio		hello_ruby
2:15_S3_or_not.png		mail
Yoshizumi_new.drawio		monitoring_solution.png
Yoshizumi_solution13.drawio	my-portfolio-site.drawio
Yoshizumi_solution13.png	my-portfolio-site.png
Yoshizumi_solution14.png	new.drawio
batch_sample			ninjacode
business-idea.drawio		rails-hotel-search-linebot
business_idea.drawio		rails-lecture
business_idea.png		rails-react-auth
docker-compose1.yml		renew_datamonitoring.drawio
docker-practice			renew_datamonitoring.png
hello.rb			ruby-lecture
```

#### 演習２　日記を書くスクリプト

①daily.shというファイルを作成して、vimで編集していく。
```daily.sh
mbp:shellscripts kentaroyoshizumi$ touch daily.sh
mbp:shellscripts kentaroyoshizumi$ vim daily.sh
```

```terminal
#!/bin/bash    #(シバンを指定する。今回はやはりbash)

#日記データを保存するディレクトリ
directory="${HOME}/shellscripts"

#保存するディレクトリがなければ作る
if [ ! -d "$directory" ]; then
 mkdir "$directory"
fi

vim "${directory}/$(date '+%Y-%m-%d').txt"    #(shellscripsというディレクトリに日付.txtという名前で保存した後vimで編集する。)
```


#### 演習３　指定したパスのファイル一覧
```open.sh
#!/bin/bash -eu

list_recursive ()
{
 local filepath=$1
 local indent=$2

 echo "${indent}${filepath##*/}"

 if [ -d "$filepath" ]; then

 local fname
 for fname in $(ls "$filepath")
 do
　　　list_recursive "${filepath}/$
{fname}" "  $indent"
 
 done
fi
}

list_recursive "$1" ""
```