Red Hat Certified System Administrator (RHCSA) #3 シェルスクリプト: 条件文、ループ、引数、終了コード

読了 9分

#2 必須ツール で redirection・パイプ・find・grep を使ってシェルを扱う手を作ったなら、この記事ではそれらのコマンドをまとめて 1 つの作業として自動化するシェルスクリプト を押さえます。RHCSA 試験には「簡単なシェルスクリプトを書け」という作業が定番として出題されます。大げさなプログラミングではなく、引数を受け取って条件によって分岐し、複数の対象をループ処理し、終了コードを正しく返す 30 行前後のスクリプトです。

そのためこの記事の目標は bash 文法の全体をなぞることではなく、試験で手が止まらない程度の核心となる骨格 を確実に固めることです。shebang から関数まで、試験に実際に出る要素だけを絞って自分で打ってみます。

shebang と実行 #

スクリプトの最初の行は、どのインタープリターで実行するかを指定する shebang です。RHCSA では bash スクリプトを書くので、次の 1 行で始めます。

#!/usr/bin/bash
echo "Hello, RHCSA"

書いたファイルは実行権限がないと直接実行できません。権限を付与して実行する流れは次のとおりです。

chmod +x hello.sh
./hello.sh

権限を与えていない場合は、bash hello.sh のようにインタープリターに直接渡して実行することもできます。ただし試験で「実行可能なスクリプトを作れ」と言われたら、chmod +x で実行権限を残しておくほうが安全です。

スクリプトを書いている間は bash -x script.sh で実行すると、各行がどう展開されるかを見せてくれるので、動作が意図と違うときに原因を素早く見つけられます。

変数とクォート #

変数は等号の両側に空白を入れずに代入し、使うときは $ を付けます。値を読むときに中括弧で囲むと、変数名の境界がはっきりします。

name="rhcsa"
count=3
echo "$name 試験は ${count} 番目の記事です"

クォートはスクリプトの安定性の核心です。二重引用符は変数を展開し、単一引用符は中身をそのまま文字どおりに残します。

dir="/etc/my dir"
ls "$dir"      # 空白があっても 1 つの引数として渡す
ls $dir        # 2 つの引数に分かれて意図と違う動作になる
echo '$name'   # $name 自体を出力

変数を使うときに 常に二重引用符で囲む習慣 を付けると、パスに空白が混じったり値が空だったりするときに生じるエラーを防げます。

位置引数 #

スクリプトに渡した引数は $1$2 の順で受け取ります。全体の引数と個数を扱う変数も一緒に覚えておきます。

変数意味
$0スクリプト名
$1$21 番目、2 番目の位置引数
$#渡された引数の個数
"$@"すべての引数をそれぞれクォートで保持したリスト
$*すべての引数を 1 つの文字列に結合

"$@"$* の違いは、試験でループ処理をするときに重要です。引数を 1 つずつ安全に巡回するには "$@" を二重引用符で囲んで使います。

#!/usr/bin/bash
echo "スクリプト: $0"
echo "引数の個数: $#"
echo "最初の引数: $1"
for arg in "$@"; do
    echo "処理対象: $arg"
done

終了コード #

すべてのコマンドは終わるときに終了コードを残します。0 は成功で、0 以外の値は失敗を意味します。直前のコマンドの終了コードは $? で確認します。

ls /etc/passwd
echo "$?"      # 0 (成功)
ls /no/such/path
echo "$?"      # 2 (失敗)

スクリプトは exit で自分の終了コードを明示的に返します。採点スクリプトや他のコマンドがこの値で成功・失敗を判断するので、失敗時に 0 以外の値で終了する 習慣が重要です。

if [[ -z "$1" ]]; then
    echo "引数が必要です" >&2
    exit 1
fi
exit 0

エラーメッセージは上の例のように >&2 で標準エラーに出力するのが慣例です。

test と条件検査 #

条件は test コマンド、または等価な角括弧で検査します。RHEL の bash では [[ ]] を使うほうがクォート処理に安全です。

よく使うファイル検査の演算子は次のとおりです。

演算子真になる条件
-e ファイルファイルが存在する
-f ファイル通常ファイルである
-d ファイルディレクトリである
-r / -w / -x読み取り / 書き込み / 実行権限がある
-z 文字列文字列が空である
-n 文字列文字列が空でない

文字列と数値の比較は演算子が違います。文字列は =!= を、数値は -eq-ne-lt-le-gt-ge を使います。

[[ "$user" = "root" ]]      # 文字列が等しいか
[[ "$count" -gt 5 ]]        # 数値が 5 より大きいか
[[ -f /etc/fstab ]]         # ファイルが存在するか

if・elif・else #

条件分岐は if で書きます。thenfi でブロックを閉じる形を正確に覚えておきます。

#!/usr/bin/bash
file="/etc/hosts"
if [[ -f "$file" ]]; then
    echo "$file は通常ファイルです"
elif [[ -d "$file" ]]; then
    echo "$file はディレクトリです"
else
    echo "$file が見つかりません"
fi

条件の位置には角括弧だけでなくコマンド自体を置くこともできます。コマンドの終了コードが 0 なら真として扱われるので、次のようにコマンドの成否で分岐できます。

if grep -q "^myuser:" /etc/passwd; then
    echo "ユーザーが存在します"
fi

case #

値が複数の分岐に分かれるときは、caseifelif の連鎖より読みやすいです。各分岐は ) でパターンを書き、;; で閉じます。

#!/usr/bin/bash
case "$1" in
    start)
        echo "サービスを開始します" ;;
    stop)
        echo "サービスを停止します" ;;
    restart)
        echo "サービスを再起動します" ;;
    *)
        echo "使い方: $0 {start|stop|restart}" >&2
        exit 1 ;;
esac

最後の *) は、どのパターンにも一致しなかった場合を受ける既定の分岐です。引数が決められた値のうちの 1 つでなければならないときに役立ちます。

for・while・until ループ #

ループは試験で「複数のユーザーを一度に作れ」や「リストの各項目を処理せよ」という作業に直結します。

for はリストの各項目を巡回します。

for user in alice bob carol; do
    echo "ユーザー作成: $user"
done

数値の範囲は brace expansion か seq で作ります。

for n in {1..5}; do
    echo "ディスク $n"
done

while は条件が真の間ループし、until は条件が真になるまでループします。ファイルを 1 行ずつ読むときに while read がよく使われます。

while read -r line; do
    echo "行: $line"
done < /etc/hostname
count=1
until [[ "$count" -gt 3 ]]; do
    echo "試行 $count"
    count=$((count + 1))
done

コマンド置換と算術演算 #

コマンドの出力を変数に入れるときは $(...) を使います。バッククォートより入れ子と可読性で有利なので、$(...) に統一します。

today=$(date +%F)
lines=$(wc -l < /etc/passwd)
echo "$today 時点のユーザー行数: $lines"

整数の算術は $(( )) の中で行います。比較条件も (( )) で書けます。

a=7
b=3
echo "$(( a + b ))"     # 10
echo "$(( a * b ))"     # 21
if (( a > b )); then
    echo "a のほうが大きいです"
fi

read で入力を受け取る #

スクリプトがユーザーに値を尋ねて受け取るときは read を使います。-p で案内文を一緒に出力します。

#!/usr/bin/bash
read -p "ユーザー名を入力してください: " username
read -sp "パスワードを入力してください: " password
echo
echo "入力したユーザー: $username"

-s は入力を画面に表示しないので、パスワード入力に適しています。ただし試験では引数を受け取る方式のほうが多いので、read は補助として知っておけば十分です。

関数 #

繰り返す作業は関数にまとめます。関数の中でも $1$2 で関数に渡した引数を受け取ります。

#!/usr/bin/bash
log() {
    echo "[$(date +%T)] $1"
}

create_user() {
    local name="$1"
    if id "$name" &>/dev/null; then
        log "$name はすでに存在します"
        return 1
    fi
    useradd "$name" && log "$name 作成完了"
}

log "スクリプト開始"
create_user alice

関数の中の変数は local で宣言して、関数の外と衝突しないようにしておくのが安全です。関数は return で終了コードを返します。

&& と || #

コマンドを連結する &&|| は、短い分岐を 1 行で表現します。&& は前のコマンドが成功したとき、|| は失敗したときに後ろのコマンドを実行します。

mkdir -p /data && echo "ディレクトリ準備完了"
id myuser &>/dev/null || useradd myuser

上の 2 行目は「ユーザーがいなければ作る」という冪等な作業を 1 行で表現したもので、スクリプトでよく使う形です。

実戦: 引数検証とループ処理スクリプト #

ここまでの要素をまとめて、引数で受け取ったユーザーを検証してから一度に作る スクリプトを書きます。試験で出る「スクリプト作成」作業の典型を盛り込んだ例です。

#!/usr/bin/bash
#
# 使い方: ./mkusers.sh user1 user2 ...
# 渡されたユーザーを作成し、結果を終了コードで報告します。

# 1) 引数検証: 1 つもなければ使い方を案内して失敗で終了
if [[ "$#" -eq 0 ]]; then
    echo "使い方: $0 ユーザー名 [ユーザー名 ...]" >&2
    exit 1
fi

# 2) root 権限の確認: useradd は権限が必要
if [[ "$(id -u)" -ne 0 ]]; then
    echo "このスクリプトは root で実行する必要があります" >&2
    exit 1
fi

fail=0

# 3) 渡されたすべての引数を巡回して処理
for user in "$@"; do
    if id "$user" &>/dev/null; then
        echo "スキップ: $user はすでに存在します"
        continue
    fi

    if useradd "$user"; then
        echo "作成: $user"
    else
        echo "失敗: $user 作成中にエラー" >&2
        fail=$(( fail + 1 ))
    fi
done

# 4) 失敗が 1 つでもあれば 0 以外のコードで終了
if (( fail > 0 )); then
    echo "$fail 人の作成に失敗しました" >&2
    exit 1
fi

echo "すべてのユーザーの処理完了"
exit 0

このスクリプト 1 つにこの記事の核心がすべて入っています。$# で引数の個数を検証し、"$@" で引数を安全に巡回し、if で分岐し、continue で次の項目へ進み、算術 (( )) で失敗数を数え、exit で終了コードを明確に返します。試験で要求されるスクリプトはたいていこの骨格の変形です。

試験ポイント #

  • shebang #!/usr/bin/bash で始め、chmod +x で実行権限を残す。 実行可能なスクリプトを作れという要求を見落としません。
  • 変数は常に "$var" でクォートで囲む。 空白・空の値による誤動作を防ぎます。
  • 位置引数 $1"$@"$# を正確に区別する。 ループ処理は for x in "$@" の形が安全です。
  • 終了コードは成功 0、失敗は 0 以外の値で exit する。 採点が終了コードを見る場合があります。
  • ifthenficaseesacfordodone の組を覚える。 閉じるキーワードを抜かすミスがよくあります。
  • 文法で詰まったら man bash/CONDITIONAL 節を見る。 インターネットなしで条件演算子を確認する道です。
  • 作成中は bash -x script.sh で展開過程を見ながらデバッグする。

まとめ #

この記事で押さえたこと:

  • shebang・変数のクォート・位置引数。スクリプトの入力と骨格を作る土台
  • 終了コード ($?exit)。成功・失敗を 0 と 0 以外の値で知らせる約束
  • 条件 (test[[ ]]ifcase) とループ (forwhileuntil)。分岐と巡回の核心となる構文
  • コマンド置換 $()・算術 (( ))read・関数・&&||。スクリプトを実用的にする道具
  • 実戦スクリプト。引数を検証し、複数の対象をループ処理し、終了コードで結果を報告する典型

RHCSA のスクリプト作業は華やかな文法ではなく、引数を受け取って条件によって処理し、結果を正しく返す 基本で解けます。上の骨格を手に馴染ませておけば、試験で詰まりません。

次へ: 起動とシステム #

シェルとスクリプトという作業の土台を押さえました。ここからシステム自体が立ち上がって動く仕組みに入ります。

#4 起動とシステム: systemd、target、GRUB2、password recovery では、systemd が起動を導く流れと target の切り替え、GRUB2 ブートローダーの設定、そして試験の定番である root パスワードの復旧手順まで、自分でたどりながら整理します。

X