Bashプログラミング
 Computer >> コンピューター >  >> プログラミング >> Bashプログラミング

シェルトレース活用ガイド:シェルスクリプトのコマンド実行をトレースしてデバッグする方法

シェルスクリプトデバッグシリーズの本記事では、3つ目のデバッグモードであるシェルトレース(shell tracing)について解説します。具体的な例を通じて、その仕組みと実際の使い方を見ていきましょう。

シリーズの前回までの記事では、残り2つのデバッグモードであるverboseモード構文チェックモードについて、わかりやすい例とともにシェルスクリプトのデバッグを有効化する方法を紹介しました。

  1. Linuxでシェルスクリプトのデバッグモードを有効にする方法 – Part 1
  2. シェルスクリプトで構文チェックによるデバッグを行う方法 – Part 2

シェルトレースとは何か

シェルトレースとは、シェルスクリプト内のコマンドの実行過程を追跡することです。シェルトレースを有効にするには、デバッグオプション -x を使用します。

このオプションを指定すると、シェルは実行されるすべてのコマンドとその引数を、実行のタイミングでターミナル上に表示します。

サンプルスクリプトの準備

ここでは、以下の sys_info.sh シェルスクリプトを使用します。このスクリプトは、システムの日付と時刻、ログイン中のユーザー数、システムの稼働時間(uptime)を簡潔に出力するものですが、修正すべき構文エラーが含まれています。

#!/bin/bash
#script to print brief system info

ROOT_ID="0"

DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`

check_root(){
    if [ "$UID" -ne "$ROOT_ID" ]; then
        echo "You are not allowed to execute this program!"
        exit 1;    
}

print_sys_info(){
    echo "System Time    : $DATE"
    echo "Number of users: $NO_USERS"
    echo "System Uptime  : $UPTIME
}

check_root
print_sys_info

exit 0

ファイルを保存し、スクリプトに実行権限を付与します。このスクリプトはrootユーザーのみが実行できるため、sudoコマンドを使って以下のように実行します。

$ chmod +x sys_info.sh
$ sudo bash -x sys_info.sh

上記の出力から、コマンドが先に実行され、その出力が変数の値として代入されるという動作が確認できます。

たとえば、date コマンドが最初に実行され、その出力が変数 DATE の値として置き換えられています。

構文チェックでエラーを特定する

次に、構文チェックを実行して、構文エラーのみを表示してみましょう。

$ sudo bash -n sys_info.sh 

スクリプトを注意深く見ると、if文 に対応する閉じの fi が欠けていることがわかります。これを追加すると、スクリプトは以下のようになります。

#!/bin/bash
#script to print brief system info

ROOT_ID="0"

DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`

check_root(){
    if [ "$UID" -ne "$ROOT_ID" ]; then
        echo "You are not allowed to execute this program!"
        exit 1;
   fi    
}

print_sys_info(){
    echo "System Time    : $DATE"
    echo "Number of users: $NO_USERS"
    echo "System Uptime  : $UPTIME
}

check_root
print_sys_info

exit 0

ファイルを保存し直し、rootとして再度構文チェックを実行します。

$ sudo bash -n sys_info.sh

しかし、構文チェックの結果にはまだ 21行目 にバグが1つ残っていると表示されます。つまり、まだ修正すべき箇所があるということです。

もう一度スクリプトを分析してみると、21行目 のエラーは、print_sys_info 関数内の最後のechoコマンドで閉じのダブルクォート (") が抜けていることに起因しています。

echo コマンドに閉じのダブルクォートを追加してファイルを保存します。修正後のスクリプトは以下の通りです。

#!/bin/bash
#script to print brief system info

ROOT_ID="0"

DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`

check_root(){
    if [ "$UID" -ne "$ROOT_ID" ]; then
        echo "You are not allowed to execute this program!"
        exit 1;
    fi
}

print_sys_info(){
    echo "System Time    : $DATE"
    echo "Number of users: $NO_USERS"
    echo "System Uptime  : $UPTIME"
}

check_root
print_sys_info

exit 0

改めてスクリプトの構文チェックを行います。

$ sudo bash -n sys_info.sh

今度は何も出力されません。これはスクリプトが構文的に正しくなったことを意味します。念のため、もう一度トレース付きでスクリプトを実行すれば、問題なく動作することが確認できます。

$ sudo bash -x sys_info.sh

続いて、スクリプトを実際に実行してみます。

$ sudo ./sys_info.sh

シェルスクリプトの実行トレースが重要な理由

シェルスクリプトのトレースは、構文エラーの特定だけでなく、それ以上に論理エラーの発見に役立ちます。例として、sys_info.sh 内の check_root 関数を挙げてみましょう。この関数は、スクリプトの実行がスーパーユーザーに限定されているため、ユーザーがrootかどうかを判定する役割を持っています。

check_root(){
    if [ "$UID" -ne "$ROOT_ID" ]; then
        echo "You are not allowed to execute this program!"
        exit 1;
    fi
}

ここでのポイントは、if文の条件式 [ "$UID" -ne "$ROOT_ID" ] です。適切な数値比較演算子(この場合は「等しくない」を意味する -ne)を使用しないと、論理エラーが発生する可能性があります。

仮に -eq(「等しい」の意味)を使用したとしましょう。そうすると、rootユーザーだけでなく一般ユーザーも誰でもスクリプトを実行できてしまい、これがまさに論理エラーとなります。

check_root(){
    if [ "$UID" -eq "$ROOT_ID" ]; then
        echo "You are not allowed to execute this program!"
        exit 1;
    fi
}

注意:シリーズの冒頭で見たように、シェル組み込みコマンドの set を使うと、スクリプトの特定のセクションだけデバッグを有効化できます。

したがって、以下のように記述することで、関数の実行をトレースし、この論理エラーを発見できます。

論理エラーを含むスクリプト:

#!/bin/bash
#script to print brief system info

ROOT_ID="0"

DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`

check_root(){
    if [ "$UID" -eq "$ROOT_ID" ]; then
        echo "You are not allowed to execute this program!"
        exit 1;
    fi
}

print_sys_info(){
    echo "System Time    : $DATE"
    echo "Number of users: $NO_USERS"
    echo "System Uptime  : $UPTIME"
}

#turning on and off debugging of check_root function
set -x ; check_root;  set +x ;
print_sys_info

exit 0

ファイルを保存してスクリプトを実行すると、下の出力のように、一般ユーザーが sudo なしでスクリプトを実行できてしまうことがわかります。これは、USER_ID の値が 100 であり、rootの ROOT_ID0)と等しくないためです。

$ ./sys_info.sh

まとめ

以上で、シェルスクリプトデバッグシリーズは完結です。-x オプションによるシェルトレースや set -x / set +x による部分的なデバッグを活用すれば、構文エラーはもちろん、一見気づきにくい論理エラーも効率的に発見できます。本ガイドやシリーズ全体に関するご質問・フィードバックがあれば、ぜひお寄せください。

  1. PowerShellスクリプトに一時停止(ポーズ)を追加する方法【具体例つき】

    この記事では、PowerShellスクリプトに一時停止(ポーズ)を追加する方法を解説します。 一時停止や待機コマンドを追加する方法はいくつかあります。以下では、それぞれの方法を具体例とともにわかりやすく紹介していきます。 PowerShellスクリプトに一時停止を追加する基本の方法 最も基本的なのは、Start-Sleepコマンドレットを使う方法です。以下の手順で追加できます。 スタートメニューから「PowerShell ISE」を開きます 一時停止コマンドを追加したいPowerShellスクリプトを開きます 一時停止させたい箇所に、start-sleep -Seconds 5という行を追

  2. 実用的な5つの例で学ぶBashスクリプト入門チュートリアル

    Unix SedやUnix Awkシリーズと同様に、本シリーズではBashスクリプティングに関する記事を複数回にわたって公開します。実用的なサンプルを交えながら、Bashスクリプティングのテクニックを幅広く解説していきます。 シェルとは、ユーザーが入力したコマンドを解釈して実行するプログラムです。コマンドはユーザーが直接入力するか、「シェルスクリプト」と呼ばれるファイルから読み込まれます。 ユーザーからの入力を直接読み取る場合、そのシェルは対話型(インタラクティブ)シェルと呼ばれます。 一方、ファイルからコマンドを読み込んで実行する場合は非対話型(ノンインタラクティブ)シェルと呼ばれます。この