hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

bashスクリプトの書き方



1 はじめに

bsahのサンプルスクリプトです。
なお、bash変数展開については、bash変数展開の使い方 - hana_shinのLinux技術ブログを参照ください。

マニュアルは以下のページを参照しました。
Bash Reference Manual

2 検証環境

CentOS版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
3.10.0-1160.el7.x86_64

bashの版数は以下のとおりです。

[root@server ~]# bash --version
GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
-snip-

3 Hello World

"Hello World"を出力するスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
echo "Hello World"

パーミッションを設定します。

[root@server ~]# chmod 700 tp.sh

スクリプトを実行します。

[root@server ~]# ./tp.sh
Hello World

4 引数の使い方

実行するスクリプトに引数を指定することができます。引数の意味は、次のとおりです。

  • $0:スクリプトのファイル名そのものを表す
  • $1,$2,$3...:引数を表す

サンプルスクリプトを以下に示します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
echo "$0" "$1" "$2"

スクリプトを実行します。スクリプトのファイル名とスクリプトに指定した引数が出力されていることがわかります。

[root@server ~]# ./tp.sh aa bb
./tp.sh aa bb

5 if文の使い方

スクリプトを作成します。

5.1 if~fi

[root@server ~]# cat tp.sh
#!/usr/bin/bash

if [ "$1" -eq 1 ]; then
  echo 'one'
fi

スクリプトを実行します。

[root@server ~]# ./tp.sh 1
one
[root@server ~]# ./tp.sh 2
[root@server ~]#

5.2 if~else~fi

[root@server ~]# cat tp.sh
#!/usr/bin/bash

if [ "$1" -eq 1 ]; then
  echo 'one'
else
  echo 'other'
fi

スクリプトを実行します。

[root@server ~]# ./tp.sh 1
one
[root@server ~]# ./tp.sh 2
other

5.3 if~elif~else~fi

[root@server ~]# cat tp.sh
#!/usr/bin/bash

if [ "$1" -eq 1 ]; then
  echo 'one'
elif [ "$1" -eq 2 ]; then
  echo 'two'
else
  echo 'other'
fi

スクリプトを実行します。

[root@server ~]# ./tp.sh 1
one
[root@server ~]# ./tp.sh 2
two
[root@server ~]# ./tp.sh 3
other

6 for文の使い方

for文は以下のように使います。ここでは、リストにはブレース展開を使ってみます。

for 変数 in リスト
do
  処理
done

6.1 リストを使った繰り返し

echoを3回実行するスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
for i in {1..3}
do
   echo $i
done

スクリプトを実行します。

[root@server ~]# ./tp.sh
1
2
3

6.2 繰り返し内の処理を中断する方法(continue)

スクリプトを作成します。i=2の場合はechoコマンドは実行せず、for文の先頭から処理を実行します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
for i in {1..3}
do
  if [ $i = 2 ]; then
    continue
  fi
  echo $i
done

スクリプトを実行します。

[root@server ~]# ./tp.sh
1
3

6.3 スクリプトに渡す引数を全て使う方法

シェルの特殊変数$@は、スクリプトに渡される引数すべてを意味します。スクリプトに渡される引数すべて使用するスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
for x in "$@"
do
  echo "$x"
done

スクリプトを実行します。

[root@server ~]# ./tp.sh 11
11
[root@server ~]# ./tp.sh 11 22
11
22

7 while文の使い方

7.1 無限ループする方法

無限ループするスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
while :
do
  echo "Hello World"
  sleep 3
done

スクリプトを実行します。Ctrl+Cを押下してスクリプトの実行を終了します。

[root@server ~]# ./tp.sh
Hello World
Hello World
Hello World
^C

7.2 キー入力を受け付ける方法

[root@server ~]# cat tp.sh
#!/usr/bin/bash
while :
do
  read key
  if [ "$key" = "q" ]; then
    echo "quit"
    break
  else
    echo "$key is entered"
  fi
done

スクリプトを実行します。qを押下するとスクリプトの実行が終了します。それ以外のキーを入力した場合は、キーが入力されたことを出力してスクリプトの実行を継続します。

[root@server ~]# ./tp.sh
a
a is entered
q
quit

7.3 ファイルの中身を1行ずつ読み込む方法

ファイルの中身を1行ずつ読み込んで表示するスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
cat $1 | while read line
do
  echo $line
done

スクリプトが読み込むファイルを作成します。

[root@server ~]# cat number.txt
one
two
three

スクリプトを実行します。

[root@server ~]# ./tp.sh number.txt
one
two
three

8 ファイルを読み込む方法(source)

tp.shから関数addを定義したfunctions.shを読み込むスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
source ./functions.sh

add 1 2
echo "Ans:"$?
[root@server ~]# cat functions.sh
#!/usr/bin/bash

function add() {
 a=$(($1+$2))
 return $a
}

スクリプトを実行します。

[root@server ~]# ./tp.sh
Ans:3

9 ファイル、ディレクトリの存在有無を調べる方法

9.1 ファイルの存在有無を調べる方法(-f)

スクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
echo "$1"

if [ -f "$1" ]; then
  echo 'exist'
else
  echo 'not exist'
fi

スクリプトを実行します。/tmp/hogeが存在するので、"exist"が出力されます。

[root@server ~]# touch /tmp/hoge
[root@server ~]# ./tp.sh /tmp/hoge
/tmp/hoge
exist

スクリプトを実行します。/tmp/hogeが存在しないので、"not exist"が出力されます。

[root@server ~]# rm -f /tmp/hoge
[root@server ~]# ./tp.sh /tmp/hoge
/tmp/hoge
not exist

9.2 ディレクトリの存在有無を調べる方法(-d)

スクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash
echo "$1"

if [ -d "$1" ]; then
  echo 'exist'
else
  echo 'not exist'
fi

スクリプトを実行します。/tmp/hogeが存在するので、"exist"が出力されます。

[root@server ~]#  mkdir /tmp/hoge
[root@server ~]# ./tp.sh /tmp/hoge/
/tmp/hoge/
exist

スクリプトを実行します。/tmp/hogeが存在しないので、"not exist"が出力されます。

[root@server ~]# rm -fr /tmp/hoge
[root@server ~]# ./tp.sh /tmp/hoge/
/tmp/hoge/
not exist

9.3 AND条件を複数指定する方法(-a)

ファイル全てが存在する場合はtrue、1つでも存在しないとfalseを表示するスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

if [ -f /tmp/test1.txt -a \
     -f /tmp/test2.txt -a \
     -f /tmp/test3.txt ]; then
  echo "true"
else
  echo "false"
fi

テスト用のファイルを作成します。

[root@server ~]#  touch /tmp/test1.txt
[root@server ~]#  touch /tmp/test2.txt
[root@server ~]#  touch /tmp/test3.txt

スクリプトを実行します。

[root@server ~]# ./tp.sh
true

テスト用に作成したファイルを削除します。

[root@server ~]#  rm /tmp/test3.txt
rm: 通常の空ファイル `/tmp/test3.txt' を削除しますか? y

スクリプトを実行します。

[root@server ~]# ./tp.sh
false

9.4 OR条件を複数指定する方法(-o)

スクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

if [ -f /tmp/test1.txt -o \
     -f /tmp/test2.txt -o \
     -f /tmp/test3.txt ]; then
  echo "true"
else
  echo "false"
fi

ファイルを1つ(/tmp/test3.txt)だけ作成します。

[root@server ~]# touch /tmp/test3.txt

スクリプトを実行します。ファイルが1つ存在するので、trueと出力されることがわかります。

[root@server ~]# ./tp.sh
true

ファイルを削除します。

[root@server ~]# rm -f /tmp/test3.txt

スクリプトを実行します。ファイルが3つとも存在しないので、falseと出力されることがわかります。

[root@server ~]# ./tp.sh
false

10 関数の使い方

10.1 関数の呼び出し方法

sub1という名前の関数を呼び出すスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

function sub1() {
  echo "Hello World"
}
sub1

スクリプトを実行します。

[root@server ~]# ./tp.sh
Hello World

10.2 関数への引数の渡し方

引数を2つとるsub1という関数を定義します。そして、関数sub1に引数xxxとyyyを指定して呼び出します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

function sub1() {
  echo "$1" "$2"
}
sub1 xxx yyy

スクリプトを実行します。

[root@server ~]# ./tp.sh
xxx yyy

10.3 戻り値を取得する方法(数値編)

引数に+1をした値を戻り値として返す関数sub1を定義します。関数の呼び出し元は$?を実行することで、関数sub1の戻り値を取得することができます。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

function sub1() {
  tmp=$(($1+1))
  return "$tmp"
}
sub1 10
echo $?

関数sub1が戻り値として11を返していることがわかります。

[root@server ~]# ./tp.sh
11

10.4 戻り値を取得する方法(文字列編)

returnコマンドは、文字列を戻り値として返すことはできません。戻り値を使用する場合は、関数内で標準出力に戻り値を出力し、コマンド置換でその値を取得します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

function sub1() {
  echo "Hello World"
}
ret=$(sub1)
echo $ret

スクリプトを実行します。

[root@server ~]# ./tp.sh
Hello World

11 配列の使い方

配列は"変数名[添字]=値"でセットし、"${変数名[添字]}"で参照します。

11.1 配列への値の代入、参照方法

配列への値の代入、参照するスクリプトを作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

color[0]="red"
echo ${color[0]}

スクリプトを実行します。

[root@server ~]# ./tp.sh
red

11.2 for文で配列を参照する方法

"${配列名[@]}"とすることで、配列の全ての要素を参照することができます。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

color[0]="red"
color[1]="black"

for tmp in ${color[@]}
do
  echo $tmp
done

スクリプトを実行します。

[root@server ~]# ./tp.sh
red
black

11.3 値まとめて配列に代入する方法

”配列名=(値 値 値…)"とすることで、値をまとめて配列に代入することができます。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

color=(red black pink)
for tmp in ${color[@]}
do
  echo $tmp
done

スクリプトを実行します。

[root@server ~]# ./tp.sh
red
black
pink

11.4 配列に値を追加する方法

”配列名+=(値 値 値…)"とすることで、配列に値を追加することができます。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

color=(red black pink)
color+=(white green)
for tmp in ${color[@]}
do
  echo $tmp
done

スクリプトを実行します。配列に追加したwhiteとgreenが出力されていることがわかります。

[root@server ~]# ./tp.sh
red
black
pink
white
green

12 連想配列の使い方

連想配列は、"配列名[キー]=バリュー"として定義します。キーの部分には、文字列を指定することができます。

12.1 バリューを出力する方法

配列名がscore、キーに名前、バリューに得点を定義した連想配列を作成します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

declare -A score
score["tanaka"]=60
score["kato"]=100
for val in ${score[@]}
do
  echo $val
done

スクリプトを実行します。

[root@server ~]# ./tp.sh
60
100

12.2 キーを出力する方法

キーの値を出力する場合、配列名の前に"!"を付けて、"for 変数名 in ${!配列名[@]}"で指定します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

declare -A score
score["tanaka"]=60
score["kato"]=100
for key in ${!score[@]}
do
  echo $key
done

スクリプトを実行します。

[root@server ~]# ./tp.sh
tanaka
kato

13 シグナルを受信する方法(trap)

シグナルを受信したら、trapコマンドを実行するスクリプトを作成します。SIGTERM,SIGHUP を受信したら、echoコマンドでメッセージを出力します。SIGINTを受信した場合、メッセージを出力した後、exitコマンドを実行してスクリプトを終了します。

[root@server ~]# cat tp.sh
#!/usr/bin/bash

trap "echo SIGTERM signal received." TERM
trap "echo SIGHUP signal received." HUP
trap "echo 'SIGINT signal received.'; exit" INT

while true
do
  echo "Running..."
  sleep 5
done

スクリプトを実行します。

[root@server ~]# ./tp.sh
Running...

もう1つターミナルを開いてpsコマンドを実行します。スクリプトのPIDを確認すると、PIDが11689であることがわかります。

[root@server ~]# ps -C tp.sh
    PID TTY          TIME CMD
  11689 pts/0    00:00:00 tp.sh

スクリプトに対してSIGTERM,SIGHUPシグナルを送信します。

[root@server ~]# kill -SIGTERM 11689
[root@server ~]# kill -SIGHUP 11689

SIGTERM,SIGHUPシグナルを受信するメッセージが出力されていることがわかります。Ctrl+cを押下するとスクリプトがSIGINTを受信します。そしてexitコマンドを実行してスクリプトを終了していることがわかります。

[root@server ~]# ./tp.sh
Running...
SIGTERM signal received.
Running...
Running...
SIGHUP signal received.
Running...
^CSIGINT signal received.

13 デバッグ方法

シェルスクリプトが正しく動作しない場合のデバッグ方法を説明します。

13.1 シェルスクリプトの構文チェック

shellcheckコマンドを使って、シェルスクリプトの構文を静的にチェックすることができます。shellcheckコマンドの使い方は、以下を参照してください。
ShellCheckコマンドの使い方 - hana_shinのLinux技術ブログ

13.2 トレースオプションを指定する方法(-x)

1行目に-xオプションを指定して、シェルスクリプトを実行しみます。

[root@server ~]# cat tp.sh
#!/usr/bin/bash -x
echo "$1"

if [ -d "$1" ]; then
  echo 'exist'
else
  echo 'not exist'
fi

シェルスクリプトを実行します。実行したコマンドと実行結果が表示されていることがわかります。

[root@server ~]# ./tp.sh /tmp
+ echo /tmp
/tmp
+ '[' -d /tmp ']'
+ echo exist
exist

13.3 ソースコードも一緒に出力する方法(-v)

"-v"は、ソースコードを出力するオプションです。”-x”と一緒に使うことで、どのコードを実行したのかがわかります。

[root@server ~]# cat tp.sh
#!/usr/bin/bash -xv
echo "$1"

if [ -d "$1" ]; then
  echo 'exist'
else
  echo 'not exist'
fi

シェルスクリプトを実行します。

[root@server ~]# ./tp.sh /tmp
#!/usr/bin/bash -xv
echo "$1"
+ echo /tmp
/tmp

if [ -d "$1" ]; then
  echo 'exist'
else
  echo 'not exist'
fi
+ '[' -d /tmp ']'
+ echo exist
exist

13.4 シェルスクリプトを変更せずにデバッグする方法(-x)

[root@server ~]# cat tp.sh
#!/usr/bin/bash -x
echo "$1"

if [ -d "$1" ]; then
  echo 'exist'
else
  echo 'not exist'
fi

"bash -x スクリプト"と実行することで、シェルスクリプトに変更を加えずにデバッグを行うことができます。

[root@server ~]# bash -x ./tp.sh /tmp
+ echo /tmp
/tmp
+ '[' -d /tmp ']'
+ echo exist
exist

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ