スクリプトのデバッグ

この章では、シェル スクリプトをデバッグする方法について説明します。

よくある間違い

シェルスクリプトを書くときは、コマンドが失敗したときの状況を考慮する必要があり、そうしないと間違いを犯しやすくなります。

#!/bin/bash

dir_name=/パス/存在しない/存在する

cd $ディレクトリ名
rm*

上記のスクリプトでは、ディレクトリ $dir_name が存在しない場合、cd $dir_name コマンドは実行に失敗します。この時点では、現在のディレクトリは変更されず、スクリプトは引き続き実行され、rm * コマンドによって現在のディレクトリ内のすべてのファイルが削除されます。

以下のように変更した場合も問題が発生します。

cd $ディレクトリ名 && rm *

上記のスクリプトでは、cd $dir_name が正常に実行された場合にのみ、rm * が実行されます。ただし、変数 $dir_name が空の場合、cd はユーザーのホーム ディレクトリに入り、ユーザーのホーム ディレクトリ内のすべてのファイルを削除します。

以下の書き方が正しいです。

[[ -d $dir_name ]] && cd $dir_name && rm *

上記のコードでは、最初にディレクトリ $dir_name が存在するかどうかが判断され、その後、他の操作が実行されます。

どのファイルを削除すればよいかわからない場合は、まず印刷して確認してください。

[[ -d $dir_name ]] && cd $dir_name && echo rm *

上記のコマンドで、echo rm * はファイルを削除しませんが、削除するファイルを出力するだけです。

bash-x パラメータ

「bash」の「-x」パラメータを使用すると、コマンドの各行を実行する前にコマンドを出力できます。これにより、何か問題が発生した場合の追跡が容易になります。

以下はスクリプト「script.sh」です。

#script.sh
エコーハローワールド

-x パラメータを指定すると、各コマンドを実行する前にコマンドが表示されます。

$ bash -x スクリプト.sh
+ エコーハローワールド
こんにちは世界

上記例において、「+」で始まる行は、その行が実行対象のコマンドであることを示しており、次の行がコマンドの実行結果であることを示している。

スクリプト内に記述された -x の別の例を見てみましょう。

#! /bin/bash -x
#trouble: 一般的なエラーを示すスクリプト

数値=1
if [ $number = 1 ];
  echo "数値は 1 に等しい"
それ以外
  echo "数値が 1 に等しくありません。"
フィ

上記のスクリプトを実行すると、各行のコマンドが出力されます。

$トラブル
+数値=1
+ '[' 1 = 1 ']'
+ echo '数値は 1 に等しい。'
数値は 1 に等しい。

出力コマンドの前の「+」記号はシステム変数「PS4」によって決まり、この変数は変更できます。

$export PS4='$LINENO + '
$トラブル
5 + 数値 = 1
7 + '[' 1 = 1 ']'
8 + echo '数値は 1 に等しい。'
数値は 1 に等しい。

さらに、「set」コマンドはシェルの動作パラメータを設定することもできます。これはスクリプトのデバッグに役立ちます。詳細については、「set コマンド」の章を参照してください。

環境変数

デバッグによく使用される環境変数がいくつかあります。

###リノ

変数 LINENO はスクリプト内の行番号を返します。

#!/bin/bash

echo "これは $LINENO 行です"

上記のスクリプトtest.shを実行すると、$LINENO3を返します。

$ ./test.sh
ここは3行目です

関数名

変数 FUNCNAME は、現在の関数呼び出しスタックを含む配列を返します。配列のメンバー 0 は現在呼び出されている関数、メンバー 1 は現在の関数を呼び出した関数、というようになります。

#!/bin/bash

関数 func1()
{
  echo "func1: FUNCNAME0 は ${FUNCNAME[0]}"
  echo "func1: FUNCNAME1 は ${FUNCNAME[1]}"
  echo "func1: FUNCNAME2 は ${FUNCNAME[2]}"
  機能2
}

関数 func2()
{
  echo "func2: FUNCNAME0 は ${FUNCNAME[0]}"
  echo "func2: FUNCNAME1 は ${FUNCNAME[1]}"
  echo "func2: FUNCNAME2 は ${FUNCNAME[2]}"
}

機能1

上記のスクリプト「test.sh」を実行すると、結果は以下のようになります。

$ ./test.sh
func1: FUNCNAME0 は func1 です
func1: FUNCNAME1 がメインです
func1: FUNCNAME2 は
func2: FUNCNAME0 は func2 です
func2: FUNCNAME1 は func1 です
func2: FUNCNAME2 がメインです

上記の例では、func1 が実行されるとき、変数 FUNCNAME のメンバー 0 は func1 であり、メンバー 1 は func1 を呼び出すメイン スクリプト main です。 func2 が実行されると、変数 FUNCNAME のメンバ 0 は func2、メンバ 1 は func2 を呼び出す func1 になります。

BASH_SOURCE

変数 BASH_SOURCE は、現在のスクリプト呼び出しスタックを含む配列を返します。配列のメンバー 0 は現在実行されているスクリプト、メンバー 1 は現在のスクリプトを呼び出すスクリプト、というようになり、変数 FUNCNAME と 1 対 1 で対応します。

以下には 2 つの添え字 lib1.shlib2.sh があります。

#lib1.sh
関数 func1()
{
  echo "func1: BASH_SOURCE0 は ${BASH_SOURCE[0]}"
  echo "func1: BASH_SOURCE1 は ${BASH_SOURCE[1]}"
  echo "func1: BASH_SOURCE2 は ${BASH_SOURCE[2]}"
  機能2
}
#lib2.sh
関数 func2()
{
  echo "func2: BASH_SOURCE0 は ${BASH_SOURCE[0]}"
  echo "func2: BASH_SOURCE1 は ${BASH_SOURCE[1]}"
  echo "func2: BASH_SOURCE2 は ${BASH_SOURCE[2]}"
}

次に、メイン スクリプト main.sh が上記 2 つのサブスクリプトを呼び出します。

#!/bin/bash
#main.sh

ソースライブラリ1.sh
ソースライブラリ2.sh

機能1

メイン スクリプト main.sh を実行すると、次の結果が得られます。

$ ./main.sh
func1: BASH_SOURCE0 は lib1.sh です
func1: BASH_SOURCE1 は ./main.sh です
func1: BASH_SOURCE2 は
func2: BASH_SOURCE0 は lib2.sh です
func2: BASH_SOURCE1 は lib1.sh です
func2: BASH_SOURCE2 は ./main.sh です

上記の例では、関数 func1 を実行するとき、変数 BASH_SOURCE のメンバー 0 は、func1 が配置されているスクリプト lib1.sh であり、メンバー 1 は関数 を実行するときのメイン スクリプトmain.sh です。 func2 、変数 BASH_SOURCE のメンバー 0 は func2 が配置されているスクリプト lib2.sh、メンバー 1 は func2 を呼び出すスクリプト lib1.sh です。

BASH_LINENO

変数 BASH_LINENO は、呼び出しの各ラウンドに対応する行番号を内容とする配列を返します。 ${BASH_LINENO[$i]}${FUNCNAME[$i]} は 1 対 1 に対応しています。これは、${FUNCNAME[$i]} がそのスクリプト ファイル $ を呼び出していることを意味します。 {BASH_SOURCE[$ i+1 の行番号]}

以下には 2 つの添え字 lib1.shlib2.sh があります。

#lib1.sh
関数 func1()
{
  echo "func1: BASH_LINENO は ${BASH_LINENO[0]}"
  echo "func1: FUNCNAME は ${FUNCNAME[0]}"
  echo "func1: BASH_SOURCE は ${BASH_SOURCE[1]}"

  機能2
}
#lib2.sh
関数 func2()
{
  echo "func2: BASH_LINENO は ${BASH_LINENO[0]}"
  echo "func2: FUNCNAME は ${FUNCNAME[0]}"
  echo "func2: BASH_SOURCE は ${BASH_SOURCE[1]}"
}

次に、メイン スクリプト main.sh が上記 2 つのサブスクリプトを呼び出します。

#!/bin/bash
#main.sh

ソースライブラリ1.sh
ソースライブラリ2.sh

機能1

メイン スクリプト main.sh を実行すると、次の結果が得られます。

$ ./main.sh
func1: BASH_LINENO は 7
func1: FUNCNAME は func1 です
func1: BASH_SOURCE は main.sh です
func2: BASH_LINENO は 8
func2: FUNCNAME は func2 です
func2: BASH_SOURCE は lib1.sh です

上記の例では、関数「func1」は「main.sh」の 7 行目で呼び出され、関数「func2」は「lib1.sh」の 8 行目で呼び出されます。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0