Shell スクリプティングの基礎

ShellBeginner
オンラインで実践に進む

はじめに

シェルスクリプトは、コマンドをファイルに保存し、同じ作業を一貫して繰り返せるようにします。信頼性の高い初心者向けスクリプトでは、使用するインタープリターを明示し、変数展開をクォートし、処理を実行する前に入力を検証し、他のコマンドが解釈できる終了ステータスを返します。

この実験では、小さな Bash スクリプトを段階的に作成します。Bash 経由および実行可能ファイルとしてスクリプトを実行し、変数と位置パラメーターを使い、対話形式の入力を読み取り、if とファイルテストで分岐し、終了コードを選び、for で反復処理を行います。最後に、これらの考え方を簡単なログ要約スクリプトにまとめます。

最初のスクリプトを作成して実行する

このステップでは、Bash スクリプトを作成し、インタープリター経由で実行した後、ファイルを直接実行できるようにします。

実験用ワークスペースに移動します。

cd /home/labex/project/shell-lab

here-document を使って hello.sh を作成します。1 行目は shebang です。ファイルを直接実行したときに Bash を使うよう指定します。

cat > hello.sh <<'EOF'
#!/bin/bash

echo "Hello from a Bash script"
pwd
EOF

実行する前にファイルの内容を確認します。

cat hello.sh

ファイルを Bash に明示的に渡して実行します。実行権限を追加する前でも動作します。

bash hello.sh

実行権限を追加し、モードを確認します。

chmod u+x hello.sh
ls -l hello.sh

現在のディレクトリにあるファイルを実行します。./ はパスを指定します。シェルは通常、現在のディレクトリを自動的には検索しません。

./hello.sh

変数と安全なクォートを使う

このステップでは、変数に値を代入して展開し、ダブルクォートによってスペースを含む値が保護される理由を確認します。

2 つの変数を使うスクリプトを作成します。

cat > variables.sh <<'EOF'
#!/bin/bash

course="Linux for Noobs"
workspace="/home/labex/project/shell-lab"

echo "Course: $course"
echo "Workspace: $workspace"
printf 'Quoted argument count: %s\n' "$#"
printf 'First argument: %s\n' "${1:-not provided}"
EOF

シェルで変数を代入するとき、= の前後にスペースを入れません。ダブルクォートを使うと、$course は展開されながら、Linux for Noobs は 1 つの値として扱われます。

スペースを含む 1 つの引数を付けてスクリプトを実行します。

bash variables.sh "beginner learner"

特殊変数 $# は位置パラメーターの数を表し、$1 は最初の引数を表します。${1:-not provided} は、最初の引数がない場合に代替値を指定します。

確認できるように出力を保存します。

bash variables.sh "beginner learner" > variable-output.txt
cat variable-output.txt

位置パラメーターを検証する

このステップでは、引数を必須にし、使い方を表示し、失敗時と成功時に異なる終了ステータスを返します。

greet-argument.sh を作成します。

cat > greet-argument.sh <<'EOF'
#!/bin/bash

if [ "$#" -ne 1 ]; then
  echo "Usage: $0 NAME" >&2
  exit 2
fi

name=$1
echo "Hello, $name"
exit 0
EOF

テストコマンド [ ... ] は、引数の数が 1 と等しくないかどうかを確認します。>&2 は使い方の説明を標準エラーに送り、exit 2 は使い方が正しくないことを報告します。

引数なしで実行し、その直後に前のコマンドの終了ステータスを表す $? を表示します。

bash greet-argument.sh
echo "missing argument status: $?"

期待されるステータスは 2 です。正しい引数を付けて実行します。

bash greet-argument.sh "Ada Lovelace"
echo "valid argument status: $?"

成功したあいさつ文を保存します。

bash greet-argument.sh "Ada Lovelace" > argument-greeting.txt

対話形式の入力を読み取る

このステップでは、ユーザーに入力を促し、1 行を安全に読み取り、空の入力を処理します。

ask-name.sh を作成します。

cat > ask-name.sh <<'EOF'
#!/bin/bash

read -r -p "Enter your name: " name

if [ -z "$name" ]; then
  echo "A name is required" >&2
  exit 1
fi

echo "Welcome, $name"
EOF

read -r は、バックスラッシュを特別扱いせずに 1 行を読み取ります。-p はプロンプトを表示し、-z はクォートされた値が空かどうかをテストします。

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

bash ask-name.sh

プロンプトが表示されたら、次のように入力します。

Grace Hopper

Welcome, Grace Hopper と表示されます。結果を再現できる形で保存するには、1 行の入力を同じスクリプトにパイプします。

printf 'Grace Hopper\n' | bash ask-name.sh > interactive-greeting.txt
cat interactive-greeting.txt

ファイルテストで分岐する

このステップでは、ファイルテストとともに ifelifelse を使い、パスの種類を判定します。

classify-path.sh を作成します。

cat > classify-path.sh <<'EOF'
#!/bin/bash

path=${1:-}

if [ -z "$path" ]; then
  echo "Usage: $0 PATH" >&2
  exit 2
elif [ -d "$path" ]; then
  echo "directory: $path"
elif [ -f "$path" ]; then
  echo "file: $path"
else
  echo "missing: $path" >&2
  exit 1
fi

exit 0
EOF

-d はディレクトリかどうかをテストし、-f は通常のファイルかどうかをテストします。順序が重要です。パスの種類を判定する前に、使い方が正しいかどうかを処理しています。

3 種類のパスの結果をすべて確認します。

bash classify-path.sh logs
bash classify-path.sh notes.txt
bash classify-path.sh missing.txt
echo "missing path status: $?"

成功した結果をまとめて保存します。&& は、最初のコマンドが成功した場合にだけ 2 番目のコマンドを実行します。

bash classify-path.sh logs > path-results.txt && bash classify-path.sh notes.txt >> path-results.txt
cat path-results.txt

For ループで複数のファイルを処理する

このステップでは、ログファイルを順番に処理し、それぞれに含まれる一致レコードの数を数えます。

count-errors.sh を作成します。

cat > count-errors.sh <<'EOF'
#!/bin/bash

log_dir=${1:-logs}

if [ ! -d "$log_dir" ]; then
  echo "Log directory not found: $log_dir" >&2
  exit 1
fi

for log_file in "$log_dir"/*.log; do
  error_count=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s errors=%s\n' "$(basename "$log_file")" "$error_count"
done
EOF

ループでは、各反復で一致したパスが 1 つずつ log_file に代入されます。"$log_file" をクォートすると、パスにスペースが含まれていても保護されます。basename はディレクトリのプレフィックスを取り除きます。grep -c は一致する行を数え、|| true は一致が 0 件の場合でも、より大きな厳密なワークフローが停止しないようにします。

ループを実行し、出力を保存します。

bash count-errors.sh logs | tee error-counts.txt

.log ファイルについて 1 件ずつ結果が表示されます。

検証、ループ、終了ステータスを組み合わせる

このステップでは、ディレクトリを検証し、そのログを要約し、成功または失敗を明確に報告する再利用可能なスクリプトを作成します。

summarize-logs.sh を作成します。

cat > summarize-logs.sh <<'EOF'
#!/bin/bash

input_dir=${1:-}
output_file=${2:-}

if [ -z "$input_dir" ] || [ -z "$output_file" ]; then
  echo "Usage: $0 INPUT_DIR OUTPUT_FILE" >&2
  exit 2
fi

if [ ! -d "$input_dir" ]; then
  echo "Input directory not found: $input_dir" >&2
  exit 1
fi

: > "$output_file"

for log_file in "$input_dir"/*.log; do
  lines=$(wc -l < "$log_file")
  errors=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s lines=%s errors=%s\n' "$(basename "$log_file")" "$lines" "$errors" >> "$output_file"
done

echo "Wrote summary to $output_file"
exit 0
EOF

: > "$output_file" コマンドは、ループを開始する前に出力先を安全に作成または空にします。2 つの必須引数を指定してスクリプトを実行します。

chmod u+x summarize-logs.sh
./summarize-logs.sh logs log-summary.txt
echo "summary status: $?"
cat log-summary.txt

存在しないディレクトリを指定して、失敗する場合もテストします。

./summarize-logs.sh missing log-summary.txt
echo "missing directory status: $?"

成功時のステータスは 0、ディレクトリが存在しない場合のステータスは 1 になります。

まとめ

Bash スクリプトを作成して実行し、shebang と権限を使い、クォートした変数を展開し、位置パラメーターと対話形式の入力を読み取り、意味のある終了ステータスを返しました。また、if/elif/else による分岐、ファイルとディレクトリのテスト、for によるログの反復処理を行い、これらの考え方を再利用可能な要約スクリプトにまとめました。

これらの基礎を身につけると、後の自動化処理を読みやすく、安全に再実行できるようになります。また、他のコマンドやスケジュールされたジョブからも結果を評価しやすくなります。