シェルスクリプト入門【ドットインストール】

ドットインストール様の動画を拝聴し、ShellScriptを学習しました。
その際のメモです。
動画の方はこちらから↓
http://dotinstall.com/lessons/basic_shellscript
# 1 シェルスクリプトとは？
> シェルスクリプトは、オペレーティングシステムのシェルまたはコマンドラインインタプリタ向けに書かれたスクリプトである。
> <a href="https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88" target="_blank">シェルスクリプト - Wikipediaより</a>

> シェル (shell) はオペレーティングシステム (OS) のユーザーのためにインタフェースを提供するソフトウェアであり、カーネルのサービスへのアクセスを提供する。
> <a href="https://ja.wikipedia.org/wiki/%E3%82%B7%E3%82%A7%E3%83%AB" target="_blank">シェル - Wikipediaより</a>

OSから情報を引き出したり、操作したりするスクリプトのこと。
# 2 はじめてのシェルスクリプト
今回、私はVagrant（CentOS）にて学習しました。
以下のコマンドで、その環境にて使用されているシェルを確認します。

```sh
echo $SHELL
# /bin/bash
```
私の環境では、`bash`が使用されていました。
学習用ディレクトリ内に`hello.sh`を作成し、文頭に以下の1行を書きます。
これは、シェルスクリプトを書く際の決まり文句となる。

```sh
!#/bin/bash
```
Helloという文字列を出力します。

```sh
# 文字列の出力
echo "Hello"
# 正常終了した場合、0を返す
exit 0
```

実行の際には、ファイルのあるディレクトリで以下のコマンドを入力する

```sh
./hello.sh
# Hello
```
# 3 変数（文字列）

```sh
# =前後にはスペースを入れない
s="Hello"
echo $s      # Hello
echo "$s"    # Hello
echo "${s}"  # Hello
echo $s$s    # HelloHello
echo "$s $s" # Hello Hello
echo '$s'    # $s シングルクォーテーションだと展開されない
```
# 4 数値演算

```sh
$x=10
echo `expr $x + 2`     # 12
# バッククォートで囲われたコマンドが実行される
# expr、演算子の前後にはスペースを入れる
```
ちなみに`expr`は整数計算を行う際に利用されるシェルスクリプトのコマンドです。
<a href="http://itpro.nikkeibp.co.jp/article/COLUMN/20060228/231170/?rt=nocnt" target="_blank">整数計算を行う「expr」</a>
#5 四則演算
演算子は以下の通りです。

```sh
# 引き算: +
# 引き算: -
# 割り算: /
# 掛け算: \*
# 掛け算のみ、バックスラッシュでエスケープさせる。エスケープしないとシンタックスエラーとなる
```
また、数学でもそうですが、先に計算したい足し算、引き算には`()`でくくると思います。
その場合は、以下のように書きます。
シェルスクリプトでは、`()`はエスケープさせないといけません。

```sh
# (x + 2) * 5 を計算したい
echo \( $x + 2 \) \* 5
```
変数の定義時に、`readonly`をつけると、上書きが効かなくなります。
大事な変数にはつけましょう。

```sh
readonly FILE_NAME=“test"
FILE_NAME=“test2”
# -bash: FIRST_NAME: readonly variable
# -> 上書きできないよ、というエラー
```
#6〜7 配列

```sh
a=(2 4 6)       # 配列の定義

# 1番目の配列を取り出す
echo $a         # 2

# 2番目の配列を取り出す（[]内を変更すれば、任意の順番の配列を取得できる）
echo $a{[1]}    # 4

# エラーの例（{}をつけない場合）
# $aは配列の1番目として読み込まれ、[1]は文字列と認識されてしまう
echo $a[1]      # 2[1]

# 全ての配列を出力
echo {$a[@]}    # 2 4 6

# 配列の数をカウント
echo ${#a[@]}   # 3
```

配列を使う上で、下記のようなことも覚えておく必要があります。

```sh
a=(2 4 6)

# 配列の上書き
a[2]=10             # 配列の3番目を10に上書きする

echo ${a[@]}        # 2 4 10

# 配列の要素の追加
a+=(20 30)          # 配列の要素を追加

echo ${a[@]}        # 2 4 10 20 30
```

また、配列の応用例で、UNIXコマンドの`date`関数を使い、
1週間の日付を配列で持ってきて、指定した位置の日付を出力させます。

```sh
# 日付を取得
d=(`date`)

# 4番目の値を取得（0が日曜日）
echo ${d[3]}
# 今週の水曜日が出力される
```

#8〜9 条件式
`test`コマンドは、与えた条件が正しいかどうか試験してくれます。
（`[  ]`で囲うことで省略も可能）
正常終了すると`0`が、エラーが起こると`1`を返します。

```sh
test 1 -eq 2; echo $?          1が返る
test 1 -eq 1; echo $?          0が返る
```

`$?`は直前にやった条件式の結果を返す標準変数です。
比較演算子や論理演算子については、下記リンクに詳しく述っています。
<a href="http://shellscript.sunone.me/if_and_test.html" target="_blank">if文とtestコマンド</a>
# 10~11 if文で条件分岐
if文は以下のように書きます。

```sh
# $xが60より大きかったら、OK!と出力
x=70
if test $x -gt 60
then
echo "OK!"
fi
# OK!
```
また、`test`は以下のように`[``]`で書き換えることも可能です。

```sh
# $xが60より大きかったら、OK!と出力
x=70
if [ $x -gt 60 ]; then
  echo "OK!"
fi
# OK!
```
複数条件を記載する場合は、他の言語の`else`、elseif`にあたるものを利用します。

```sh
# $xが60より大きかったら、OK!と出力
# $xが60以下で、40より大きければsoso...と出力
# $xが上記以外であればng...と出力
x=20
if [ $x -gt 60 ]; then
  echo "OK!"
elif [ $x -gt 40 ]; then
  echo "soso..."
else
  echo "ng..."
fi
# ng...
```
動画内で説明はありませんでしたが、UNIXコマンドの`which`は、**指定したコマンドのパス(場所)を検索してフルパスで表示**します。
今回利用した`[`を`which [`で検索すると、私の場合は`/usr/bin/[`と出力されます。
つまり、`/usr/bin/`以下にこのコマンドがあるということになります。
# 12 case文で条件分岐
case文は以下のように書きます。

```sh
# $xが60より大きかったら、OK!と出力
signal="red"
case $signal in
  "red")               # redの場合
    echo "stop!"
  ;;
  "yellow")            # yellowの場合
    echo "caution!"
  ;;
  "green")             # greenの場合
    echo "go!"
  ;;
  *)                   # その他の場合
    echo "..."
  ;;
esac
# stop!
```
# 13 while文でループ処理
while文は以下のように書きます。

```sh
# 1から10まで出力
i=0
while [$i -lt 10 ]
do
  i='expr $i + 1`
  echo $i
done
```

また、if文と併用して`continue`、`break`も使用できます。
`while`の後、条件文の場所を`:（ヌルコマンド）`にすると無限ループが発生します。
そちらを`continue`、`break`も利用して下記のようにします。

```sh
i=0
while :    #無限ループ開始
do
  i='expr $i + 1`
  # $iが3であればスキップ
  if [$i -eq 3 ]; then
    continue
  fi
  # $iが10より大きければループを抜ける
  if [$i -gt 10 ]; then
    break
  fi

  echo $i
done
```
# 14 for文
例えば、1〜5を順番に出力したいときは以下のように書きます。

```sh
for i in 1 2 3 4 5
do
    echo $i
done
```
`do`と`done`によって囲われた部分を繰り返します。

また、条件の部分で、配列を使用できます。

```sh
a=(1 2 3 4 5)

for i in ${a[@]}    # 配列を全て展開
do
    echo $i
done
```

そもそも条件文をいちいち書くのが面倒な場合は、`seq`が使えます。
<a href="http://d.hatena.ne.jp/katz_24/20140122/1390396525" target="_blank">シェルスクリプトで指定回数ループ処理</a>
`seq`は第1引数から第2引数までの値を出力してくれます。
第3引数を指定すれば、出力間隔も指定できます（デフォルトは1）

```sh
for i in `seq 1 100`
do
    echo $i
done
# 1〜100まで出力される
```

また、while文と同様に、`continue`、`break`も使用できます。
# 15 コマンド引数を渡す
コマンドからファイルに対して引数を渡すとします。
その場合、`$0`、$1`という特殊な変数を与えることで、引数をプログラムん与えることができます。
以下のファイルを用意します。

```sh:hello.sh
# プログラム自身の名前（ファイル名）
echo $0

# 1番目の引数を出力
echo $1

# 2番目の引数を出力
echo $2

# 全ての引数を出力
echo $@

# 全ての引数の数を出力
echo $#
```
そして、コマンドラインから以下のように入力します。

```sh
$ ./hello.sh a b
```

結果はこちらになると思います。

```sh
./hello.sh      # $0
a               # $1
b               # $2
a b             # $@
2               # $#
```

ちなみに、10以上の引数を使う場合には`{}`をつけて変数を用意します。

```sh
echo ${10}
```

# 16 ユーザーからの入力を受け付ける
例えば、ユーザーから特定の文字を入力されないと、延々と次に進めないようなプログラムを作るとします。

```sh
while :
do
    read key
    echo "you pressed $key"
    if [ $key = "end" ]; then
        break
    fi
done
```

これを実行すると、まず何も起こりませんが、ここで適当なキーを打ち込んでみてください。
例えば、`a`を打ち込みます。

```sh
you pressed a
```

上のように表示されます。
適当な文字列を打ち込んでも同様な状態になると思います。
ここで、if文で指定した`end`と押すと、終了します。

`read`コマンドを利用しましたが、これは**入力された文字列を指定された変数に格納する**標準コマンドです。
今回は`key`を変数として指定しているため、入力された文字列を出力できます。
<a href="http://itdoc.hitachi.co.jp/manuals/3021/3021313320/JPAS0385.HTM" target="_blank">readコマンド</a>

また、他にも`select`というコマンドもあります。
`select`は`for`文のようなループ系のコマンドですが、用意された配列の中からユーザーに選択してもらい、その文字列を変数に格納します。

```sh
select option in var opt home exit
do
    echo "you pressed $option"
    break
done
```

これを実行すると、以下のように表示されると思います。

```sh
1) var
2) opt
3) home
4) exit
#?
```

ここで、キーボードで`1`を押すと、

```sh
you pressed var
```

`2`を押すと、

```sh
you pressed opt
```

と出力されます。
これにif文などを組み合わせれば、このボタンでこの処理、このボタンでこの処理、とできるようです。
<a href="http://unix.oskp.net/shellscript/for_select.html" target="_blank">制御構文 FOR / SELECT</a>
# 17 別ファイルの内容を読み込む
以下のテキストファイルを用意します。

```txt
yesterday
today
tomorrow
```

このファイルを読み込んで、1行ずつ出力させます。
それでは、シェルスクリプトのファイルに以下のように書きます。

```sh
# while readで与えられたファイルを1行ずつ読み込む
while read line
do
    echo $line
# $1は第1引数のための変数
done <$1
```

そしてコマンドラインに戻り、以下のように入力します。

```sh
$ ./hello.sh data.txt
```

結果は、

```
yesterday
today
tomorrow
```

これは、第1引数で読み込むファイル名を指定して、`$1`に格納します。
`while read`で`$1`に入っている、つまり`data.txt`の行数まで繰り返し、文字列を出力します。
こちらはよく利用するようで、いろいろなパターンがあるようです。
<a href="http://server.etutsplus.com/sh-while-read-line-4pattern/" target="_blank">シェルスクリプトでよく使われる while read line 4パターン</a>
# 18 関数
関数は以下のように作成します。
（`function`は省略可能）

```sh
function hello() {
    echo "hello"
}

# 関数の呼び出し
hello
```

これで、`./hello.sh`を呼び出すと、`hello`と出力されます。
例えば、引数を利用したいときは、`$1`、`$2`などの標準変数を利用して、以下のように書き換えます。

```sh
function hello() {
    echo "hello $1 and $2"
}

# 関数の呼び出し
hello Mike Tom
```

関数内で利用する変数ですが、シェルスクリプトでは関数内で宣言しても、関数外で使用できます。

```sh
function hello() {
    echo "hello $1 and $2"
    i=5
    echo $i
}

# 関数の呼び出し
hello Mike Tom
echo $i
```

これは、関数内、関数外両方とも`echo`で出力されます。
関数内のみで使用したい場合は、変数宣言時に`local`をつけて、`local i=5`とします。
# 参考資料
- <a href="http://dotinstall.com/lessons/basic_shellscript" target="_blank">ドットインストール</a>
- <a href="http://shellscript.sunone.me/" target="_blank">UNIX & Linux コマンド・シェルスクリプト リファレンス</a>