Linuxについて [シェルスクリプト]

## シェルスクリプト

### シェル

コマンドの入力を受け付けそのコマンドを実行し、入力したユーザに対しその結果を返す役割がある

### シェルスクリプト

繰り返し行なう処理を自動化するための手段
コマンドに条件分岐や繰り返しなど制御機能を加え実行すること


たとえば

~~~
# tar cvzf 120626-etc.tar.gz /etc
# tar cvzf 120626-home.tar.gz /home
# scp 120626-etc.tar.gz root@backup.local.example.com:~/backup
# scp 120626-home.tar.gz root@backup.local.example.com:~/backup
~~~
このようなコマンドがあるとすると、
これを実行のたびに入力するのは面倒で、さらに1つのコマンドの処理が終わるまで次のコマンドを実行できなかったり、そもそも実行する際に端末の前に人手が必要である


この一連処理をsystembackup.shという名前のファイルを作成して書き込み、system-backup.shを実行することでファイルに書き込んだコマンドを順に実行できる

~~~
#!/bin/bash

tar cvzf 120626-etc.tar.gz /etc
tar cvzf 120626-home.tar.gz /home
scp 120626-etc.tar.gz root@backup.local.example.com:~/backup
scp 120626-home.tar.gzroot@backup.local.example.com:~/backup
~~~


#### プログラムの要素

* 順次実行
* 条件分岐
* 繰り返し
* サブルーチン

**このようにプログラミングには、どの種類のプログラムでもいくつかの重要な構成要素がある**


### シェルスクリプトの作成

~~~
$vi lsdate.sh
~~~
まずはlsdate.shというファイルを作る

~~~
#!/bin/bash
ls
date
~~~
lsdate.shというファイルに、lsとdateコマンドを実行するシェルスクリプトを記述

#### シェルの指定


ファイルの1行目には、利用するシェルの種類とそのコマンド位置を記述する
シェルには数種類あり、今回はbashを使用
1行目に利用するシェルを指定、2行目以降に実行するコマンドを1行ずつ入力


### パーミッションの変更

作成したシェルスクリプトを実行するには、パーミッション（アクセス権）を変更してファイルの実行権限をつける必要がある

~~~
$ ls -l lsdate.sh
-rw-rw-r--. 1 tooyama tooyama 21 Jun 6 09:51 lsdate.sh
~~~
lsコマンドでファイルのアクセス権限を確認している


~~~
$ chmod u+x lsdate.sh
$ ls -l lsdate.sh
-rwxrw-r--. 1 tooyama tooyama   21 Jun 6 09:51 lsdate.sh
~~~

実行権限を付与するためにchmodコマンドを使う
これで所有者に実行権限が与えられた

~~~
$ ./lsdate.sh
Desktop diff2     ls-l-output     ls-usr-bin
touched-file
cat-output   hosts.bak   ls-l-output-second   lsdate.sh
uniq-sample
diff1        hosts.new2  ls-output            score   （lsの実行結果）
Fri Jun 6 09:55:05 JST 2008     （dateの実行結果）
~~~
実行権限をシェルスクリプトにつけたらシェルスクリプトを実行する

**./**というのはパス指定で、「カレントディレクトリにある(lsdate.shを実行せよ)」という意味
lsやcpを実行するときは、パスが通っているので、パス指定は必要ないが、今回は、カレントディレクトリにある特定のシェルスクリプトを実行させるために、パス指定を実行
lsdate.shの中に記述された、lsコマンドとdateコマンドが順に実行された

### コメント

プログラム上に書く注釈

シェルの場合、**#**で始まる行がコメント
プログラムの実行時はコメントは無視される


~~~
$ vi lsdate.sh
#!/bin/bash
ls
#date
~~~
lsdate.shを編集して、dateコマンドをコメントアウト

~~~
$./lsdate.sh
Desktop diff2     ls-l-output     ls-usr-bin
touched-file
cat-output   hosts.bak   ls-l-output-second   lsdate.sh
uniq-sample
diff1        hosts.new2  ls-output            score（lsの実行結果）
~~~
dateコマンドの出力がなくなった

このようにコメントは記述したプログラムがどういった処理をするのかを記述したり、一時的に特定の処理を無効化（コメントアウト）する場合に利用される

### echoコマンド

#### echo [オプション] 文字列

引数で与えた文字列を標準出力に出力する

オプション|意味
-|-
-n|通常の出力は改行されるが、このオプションがあると改行されない


### 変数

変数とは、いわゆる「ハコ」の役割をし、中に数値や文字列が入る
変数に「=」を使って数値や文字列を代入し、参照は「$」をつけて行う

~~~
$ abc=123
$ echo $abc
123
（abcの内容を表示する）
~~~

シェル関数abcに値を設定し、に123を代入している

~~~
$ abc[0]=123
$ abc[1]=456
$ echo ${abc[0]}
123
（abc[0]の内容を表示する）
$ index=1
$ echo ${abc[$index]}
456
（abc[1]の内容を変数を使って表示する）
~~~

### シェル変数と環境変数

* シェル変数
  実行しているシェル内部でのみ有効

* 環境変数
  シェル内で実行されたコマンド内（外部）でも有効

~~~
$ export abc
（シェル変数abcを環境変数abcにする）
$ export xyz=234
（環境変数xyzを作成し234を代入する）
~~~
exportコマンドを使い環境変数を作成している

1つ目のコマンドではabcという環境変数を作成していて、abcには値を代入していない
2つ目のコマンドではxyzという環境変数を作成していて、xyzには234を代入している


~~~
$ cat BBB.sh
#!/bin/bash
xxx=123                          #シェル変数xxxに123を代入する
export yyy=234                   #環境変数yyyに234を代入する
echo xxx=$xxx in BBB.sh          #変数xxxの値を表示する
echo yyy=$yyy in BBB.sh          #変数yyyの値を表示する
./CCC.sh                         #CCC.shを実行する

$ cat CCC.sh
#!/bin/bash
echo xxx=$xxx in CCC.sh          #変数xxxの値を表示する
echo yyy=$yyy in CCC.sh          #変数yyyの値を表示する


$./BBB.sh
xxx=123 in BBB.sh
yyy=234 in BBB.sh
xxx= in CCC.sh
yyy=234 in CCC.sh
$
~~~

シェル変数は引き継がれないので、シェルスクリプトCCC.shの中でxxxの値は表示されない
一方で、yyyは環境変数なのでCCC.shまで引き継がれるため、値が表示される

### readコマンド

#### read 変数名

標準入力からデータを読み込み、すでに変数にデータが入っていた場合、新しいデータに上書きされる

~~~
$ echo $abc
123
$ read abc
aaabbbccc
$ echo $abc
aaabbbccc
~~~

1行目でシェル変数abcの中身が表示される
3行目で文字列を入力
5行目でシェル変数abcの中身が入れ替わっている


### シェル変数

シェル変数の一覧を表示する場合は、**setコマンド**を利用
削除する場合は、**unset**を利用

~~~
$ set
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:
hostcomplete:interactive_comments:login_shell:progcomp:promptvars:so
urcepath
BASH_ALIASES=()
abc=aaabbbccc
$ set | grep ^abc
abc=aaabbbccc
$ unset abc
$ set | grep ^abc

~~~

8行目でabcで始まるシェル変数のみ確認
9行目でシェル変数abcを削除
10行目でabcで始まるシェル変数のみ確認し、削除されていることを確認



### 環境変数

環境変数の一覧を表示する場合は、**envコマンド**を利用
登録済みの環境変数を削除するときは、**unsetコマンド**を利用

~~~
$ env
ABC=999999
HOSTNAME=host1.alpha.jp
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
（略）
$ env | grep ^ABC
ABC=999999
$ unset ABC
$ env | grep ^abc
$
~~~

8行目でABCで始まる環境変数のみ確認
9行目で環境変数ABCを削除
10行目でABCで始まる環境変数のみ確認し、削除されていることを確認


### 引用符

シェルスクリプトにおいて、文字列を引用符で囲むことが可能であり、使用される引用符により
囲まれた文字列の処理が異なる

* '(シングルクォート)
  参照用の「＄」付きの変数がある場合、「$」も文字列として認識されるため、変数は展開されない
* "(ダブルクォート)
  「$」付き変数は展開された文字列になる
* `(バッククォート)
  バッククォートで囲まれた文字列はコマンドとして解釈され、このとき「$」付きの変数があれば、それを展開した上でコマンドが実行される

~~~
$ ABC=123
$ echo 'Value of ABC is $ABC.'
Value of ABC is $ABC.
$ echo "Value of ABC is $ABC."
Value of ABC is 123.
$ XYZ=`date`;
$ echo "It is $XYZ now."
It is Thu Mar 20 06:08:14 JST 2017 now.
$ echo "Itis`date`now."
It is Thu Mar 20 06:08:14 JST 2017 now.
~~~

2行目では＄ABCが文字として認識されそのまま表示された

3行目では＄ABCが変数として認識され、内容である123が展開された
5行目では＄XYZにdateコマンドの実行結果が入っている
6行目ではダブルクォートで全体の文字列を囲み、バッククォートで囲んだ
dateコマンドを挿入して、変数XYZへの代入を省略


### 引数

シェルスクリプトは、実行時にオプションを引数として参照することが可能
引数は＄1,＄2…など$の後に引数の番号を指定することで参照できる

~~~
$ cat args.sh
#!/bin/bash

echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
echo '$0:' $0;
echo '$#:' $#;

$ ./args.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$0: ./args.sh
$#: 3
~~~

＄1-＄3は引数、＄0は実行コマンド名、$#は、引数の数を示している


### shift文

引数の順序をずらす
shiftを実行すると、＄2が＄1に、＄3が＄2に・・・になる


~~~
$ cat argsshift.sh
#!/bin/bash


echo '$1:' $1;
echo '$2:' $2;
echo '$3:' $3;
shift
echo '$1:' $1;
echo '$2:' $2;
~~~

作成したスクリプトに対して、変数を渡すと

~~~
$ ./argsshift.sh aaa bbb ccc
$1: aaa
$2: bbb
$3: ccc
$1: bbb
$2: ccc
~~~
shiftを実行すると
4行目で＄1がbbbに変わった
5行目で＄2がcccに変わった


### エスケープシーケンス

~~~
$ABC=123
$ echo "Value of ABC is "$ABC"."
Value of ABC is 123.
$ echo "Value of ABC is \"$ABC\"."
Value of ABC is "123".
~~~

2行目では＄ABCを""で囲ったが、""が表示されない
3行目では表示したい"の直前に**「\」**を付けることで"を表示できる

~~~
$ echo 'Value of ABC is "$ABC".'
Value of ABC is "123".
$ echo "Value of ABC is\$ABC."
Value of ABC is $ABC.
~~~

1行目で文字列全体を'(シングルクォート)で囲むことでも"(ダブルクォート)を表示できる
2行目でダブルクォートで囲んだ文字列内で「$」をそのまま表示したい場合にも直前の「\」で可能


\（バックスラッシュ）は**エスケープ文字**と呼ばれ、
* 特別な文字で直後の１文字の扱いを変更する
* 使用する引用符との組み合わせで、文字の扱いを変えたい場合に有効
* 改行コードにも有効で\（バックスラッシュ）を行末に付与することで、文字列の途中で折り返す
  ことができる
  \（バックスラッシュ）による改行はシェルスクリプト内でも使うことが可能

### エスケープシーケンス

次に続く１文字とセットで特別な意味を持つ文字列とする

* \t(タブ)
* \n(改行)
* \ooo(oは数字で８進数表記の文字)

などがある

~~~
$ echo -e "I am a cat. \nAs yet I have no name\041"
I am a cat.
As yet I have no name!

~~~

\nは改行に変わり、\041は!になった

※-eはエスケープした文字を解釈するechoオプション

### sourceコマンド

bashなどのシェルの内部コマンドで、指定されたファイルを読み込んでシェル環境を設定する
ファイル内容はシェルコマンドと解釈して実行
一般的な用途として、シェルの環境設定ファイルである".bashrc"や".bash_profile"などを設定変更後、ログインしなおさずに設定を現在のシェル上で有効にする場合に使われる

~~~
$ cat set.sh


#!/bin/bash
abc=xyz
echo $abc
~~~

シェルスクリプト「set.sh」を用意する
1行目でset.shの内容確認

~~~
$ echo $abc
~~~

＄abcをechoコマンドで出力
＄abcには何も格納されてないため、何も表示されない

~~~
$ ./set.sh
xyz
~~~

set.shスクリプトを実行
スクリプト内で設定された変数abcの値がechoで出力された
set.shにはecho文が記述されているため、その値が出力される

~~~
$ echo $abc
~~~
$abcをechoコマンドで出力
変数abcへの値の設定はスクリプト内でしか有効でないので、何も表示されない


~~~
$ source set.sh
xyz
~~~

sourceコマンドでset.shを読み込む
スクリプト内で設定された変数abcの値がechoで出力された

~~~
$ echo $abc
xyz
~~~

sourceコマンドで読み込んだことにより、set.shの終了後も変数abcに値が格納されたまま


## 条件分岐


### if文

#### if 条件式1 then ... elif 条件式2 ... else ... fi

elif...の部分とelse...の部分は省略可能
elifは、別の条件(条件式2)で判断したい場合に利用する
elseは条件が全てあてはまらなかった場合、実行される
fiで終了

#### 文字列比較を行う演算子

演算子|比較内容
-|-
a==b|aとbが等しければ真
a!=b|aとbが等しくなければ真

#### 数値比較を行う演算子

演算子|比較内容
-|-
a -eq b|aとbが等しい（equalto）ければ真
a -ne b|aとbが等しくなければ（notequalto）真
a -ge b|aがb以上（greaterthanorequalto）であれば真
a -le b|aがb以下（lessthanorequalto）であれば真
a -gt b|aがbより大きい（greaterthan）値であれば真
a -lt b|aがb未満（lessthan）であれば真

### ファイル属性の確認

### if test -d パス ; then.....

-dの部分がファイル属性確認の演算子
-dはディレクトリであるかの判定を行なうので、このif文全体で、「パスがディレクトリであれば真の値を返す」という条件式

#### :ファイル属性の確認を行なう演算子

演算子|内容
-|-
-f ファイル名|通常ファイルなら真
-d ファイル名|ディレクトリなら真
-e ファイル名|ファイルが存在すれば真
-L ファイル名|シンボリックリンクなら真
-r ファイル名|読み取り可能ファイルなら真
-w ファイル名|書き込み可能ファイルなら真
-x ファイル名|ファイルが存在して、実行権限があれば真
-s ファイル名|サイズが0より大きければ真


#### if[条件節]; then...(if test 条件節 ; then....)

testコマンドは[]を使って記述することも可能


### 複数の条件を重ねる


条件分岐の場合、複数の条件を重ねることができます。条件Aと条件Bが同時に成立している必要があるときは、「条件A”かつ”条件Bが成立」ということで、論理積(=AND)が用いられます。同じく「条件A”もしくは”条件Bが成立」の場合は、論理和(=OR)が用いられます。シェルスクリプトにおいて、論理積・論理和の書き方は2通り存在します。

### 論理積

条件A”かつ”条件B”かつ”条件Cが成立


#### [条件A -a 条件B -a条件C ]....
#### [条件A] && [条件B] && [条件C]...

２通りあり[]の使い方が違う

### 論理和

条件A”もしくは”条件B"もしくは”条件Cが成立

#### [条件A -o 条件B -o 条件C ]....
#### [条件A] || [条件B] || [条件C]...

### 一対多の条件分岐

シェルスクリプトでは、case文が用意されており、一対多の分岐が記述できるようになっている

~~~
case 変数 in
         値A)
                 処理1;;
         値B)
                 処理2;;
esac
~~~


変数の値が値Aのとき、処理1を実行
最後はcase文の逆からの記述である**esac**で終わる
値はパイプ記号（|）で区切って複数指定可能


~~~
$ cat case.sh

#!/bin/bash
case $1 in
          a|A)
            echo"引数にaまたはAが入力されました";;
          b|B)
            echo"引数にbまたはBが入力されました";;
esac
~~~

1行目でcase.shを作成して実行

~~~
$ ./case.sh a
引数にaまたはAが入力されました
$ ./case.sh B
引数にbまたはBが入力されました
~~~
と表示される

また、

~~~
$ cat defaultcase.sh
#!/bin/bash

case $1 in
        1)
           echo"引数に1が入力されました";;
        2)
           echo"引数に2が入力されました";;
        *)
           echo"1,2以外が入力されました";;
esac
~~~

1行目でdefaultcase.shを作成し実行

どの値にもマッチしなかった場合の処理を記述するには、値に**アスタリスク（*）**を使う

~~~
$ ./defaultcase.sh 1
引数に1が入力されました
$ ./defaultcase.sh 2
引数に2が入力されました
$ ./defaultcase.sh 0
1,2以外が入力されました
~~~

5行目でどの条件にもマッチしない場合に指定した値*の処理が実行

### 繰り返し


同じ処理を繰り返し行い、ある条件が成立したときに終了する、という形式

### for文

~~~
for 変数 in 値のリスト
do
         処理
done
~~~

値を列挙し、それを対象に処理を繰り返す
値のリストは文字を羅列したもや実行結果

~~~
$ for i in a b c d
> do
>     echo $i
> done
a
b
c
d
~~~
値のリストとして'a b c d'が渡され、まずi=aを行いecho $i、次にi=bを行いecho $iを・・・
という繰り返し

~~~
for i in `ls`
~~~

結果値のリストとしてコマンドの実行結果を使うこともでき、iにlsの実行結果を代入してループが実行される

#### while/until文

* while条件が成立している間ループを繰り返し条件が成立しなくなったら終了という処理
* until文は条件が成立してない間ループを繰り返し条件が成立したら処理を終という処理

~~~
while 条件式
do
        処理
done

until 条件式
do
        処理
done
~~~

たとえば

~~~
$ cat loop.sh

#!/bin/bash


count=1
while [ $count -le 10 ]
do
        echo "この処理は$count回実行されました"
        count=`expr$count+1`
done
~~~

のloop.shを作成

count=1で、カウンタ用の変数countに初期値1を設定
while [ $count -le 10 ]で、シェル変数countが10以下の間、処理を繰り返す

~~~
$./loop.sh
この処理は1回実行されました
この処理は2回実行されました
この処理は3回実行されました
この処理は4回実行されました
この処理は5回実行されました
この処理は6回実行されました
この処理は7回実行されました
この処理は8回実行されました
この処理は9回実行されました
この処理は10回実行されました
~~~
このような実行結果となる

### select文

~~~
select 変数 in リスト
do
       処理
done
~~~
たとえば

~~~
select name in "apple" "banana" "orange"
do
       echo "You selected $name";
done
~~~
これを実行すると

~~~
1) apple
2) banana
3) orange
$? 1
You selected apple
~~~
13を入力すると、do～done内部が実行される
また、「Ctrl」Cで中止できる

### 繰り返しの制御

**break**や**continue**を使い繰り返しを制御することができる
breakは繰り返しを終了して、continueは繰り返しの先頭に戻る役割


~~~
$ cat ./sample.sh
while true
do
  echo "Continue? (y/n)"
  read input
  case $input in
    n) break
        ;;
    y) continue
        ;;
    *) echo "Please input y or n."
        ;;
    esac
done
~~~
これを実行すると

~~~
$ ./sample.sh
Continue? (y/n)
y
Continue? (y/n)
a
Please input y or n.
Continue? (y/n)
n
$
~~~

3行目でyを入力すると
4行目で繰り返しの先頭に戻る
5行目でy,n以外を入力
6行目でPlease input y or n　が表示される
8行目でnを入力すると
9行目では繰り返しをが終了され何も表示されない


## サブルーチン

一連の処理をまとめて、再利用できるようにしたものをいう
サブルーチンはシェルスクリプトでは関数と呼ばれている

### 関数

関数は、引数とよばれるデータを与え、処理をして結果を返すという機能の集まり

~~~
function関数名
{
    処理
}
~~~

または

~~~
関数名()
{
    処理
}
~~~~~

引数は、実行時に関数名に続けて記述
引数は、関数の内部で$1,$2で参照することができる

### return文

#### return 変数名

シェルスクリプトの関数で、結果を返すときに実行
関数内での処理はそこで終了し、変数が関数の呼び出し元に返される

## デバッグ

作成したプログラムが動作しない場合はどこに問題があるのか調べる必要がある
これをデバッグという
多くのプログラム言語には、デバッグを支援するツールが用意されており、シェルスクリプトにも、スクリプトをデバッグモード
で動かすためのコマンドが用意されている

### shコマンド

sh自身はシェルを起動するコマンドで、-xオプションを付けて引数にシェルスクリプトを指定すると、コマンドや変数の中身を表示しながらスクリプトを実行

~~~
$ sh -x ./sample.sh
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
y
（yを入力）
+ case $input in
+ continue
+ true
+ echo 'Continue? (y/n)'
Continue? (y/n)
+ read input
n
（nを入力）
+ case $input in
+ break
~~~

>参考書籍

>>『Linux標準教科書 v3.0.2』 LPI-Japan 2019年