Linux 講座 / Bash シェルスクリプト作成:変数・条件文・ループ文
シェルスクリプトは、ターミナルで実行するコマンドと制御フローをファイルにまとめ、繰り返し実行するプログラムです。Bashでは変数展開をダブルクォートで囲み、入力条件や終了状態を確認しながら小さなスクリプトから作るのが良いです。
この文章では、コマンドをファイルに保存して実行する最初のプログラムを作ります。変数・終了状態・演算子の原理はシェル実行フローを、失敗・パス・同時実行の制限はBashのエラー処理と安全なファイル操作を参考にしてください。
Bashとshを区別して作業フォルダを準備する
この実習はUbuntu・Rocky LinuxのBashを対象としています。[[ ... ]]や配列などのBash機能は他のshでは動作しない可能性があるため、sh script.shで実行しないでください。まずbash --versionでインストール状況を確認し、新しいディレクトリでファイルを作成します。エディタの改行はLFで保存します。
lab=$(mktemp -d "$HOME/linux-script-lab.XXXXXX")
cd "$lab"
mkdir "sample files"
printf "one\n" > "sample files/alpha.txt"
printf "two\n" > "sample files/beta.txt"
実行可能な完全な例を作成する
以下のブロックをそのまま実行すると、現在のディレクトリにlist-files.shが作成されます。引数で受け取ったディレクトリを確認し、その中の通常のファイル名を一行ずつ出力します。ファイルを作成したり削除したりするスクリプトではないので、変数・分岐・繰り返し動作を比較するのに適しています。
cat > list-files.sh <<'EOF'
#!/usr/bin/env bash
if (( $# != 1 )); then
printf "Usage: %s DIRECTORY\n" "$0" >&2
exit 2
fi
directory=$1
if [[ ! -d "$directory" ]]; then
printf "Not a directory: %s\n" "$directory" >&2
exit 1
fi
count=0
for path in "$directory"/*; do
[[ -f "$path" ]] || continue
printf "%s\n" "${path##*/}"
count=$((count + 1))
done
printf "files=%s\n" "$count"
EOF
bash -n list-files.sh
bash list-files.sh "sample files"
予想される出力はalpha.txt、beta.txt、files=2です。ファイル名の並び順はロケールの影響を受ける場合があります。bash -nは文法だけをチェックするため、存在しないパスや権限などの実行エラーまでは検出できません。スクリプトの先頭の#!は実行環境を指定するシバンであり、コマンドの前に付けるターミナルプロンプトではありません。
変数と引用符がファイルパスを守る理由
変数代入はdirectory=$1のように、等号の周りに空白を入れずに書きます。読み取るときは"$directory"のように二重引用符を使い、空白を含むパスを一つの引数として渡します。単一引用符は通常変数展開を防ぎ、二重引用符は変数展開を許可しつつ、単語分割やファイル名展開を防ぎます。evalのように、入力をコマンドのように再解釈する必要はありません。
"$directory"/*ではパス部分を保護し、引用符の外にあるアスタリスクだけをファイル名展開(グロブ)に使います。基本的なBashの設定ではマッチがなくてもパターン文字列が残る場合があるため、[[ -f "$path" ]] || continueで条件に合わない項目をスキップします。このパターンはドットで始まる隠しファイルを含まず、ディレクトリの下を再帰的に探索せず、通常のファイルを指すシンボリックリンクは -f が真である可能性があります。
引数・条件文・ループ文と終了ステータスの読み取り
| 構文 | 意味 |
|---|---|
| $# / $1 | 引数の数 / 最初の引数 |
| [[ -d ... ]] | 対象がディレクトリかどうかチェック |
| for ...; do ...; done | 展開された各項目に同じ処理を適用 |
| ${path##*/} | 最後のスラッシュの前部分を取り除き、名前だけを取得 |
| exit 0 / 1 / 2 | この例の成功 / 入力対象エラー / 使用方法エラー |
最後のprintfが成功すれば、このスクリプトはステータス0で終了します。終了コード 1と2の具体的な意味は、このスクリプトで定めた約束であり、すべてのコマンドに同じように適用される分類ではありません。$?は直前のコマンドの終了状態なので、他のコマンドを挟む前に確認します。
bash list-files.sh
printf "exit=%s\n" "$?"
bash list-files.sh missing-directory
printf "exit=%s\n" "$?"
chmod u+x list-files.sh
./list-files.sh "sample files"
一つずつ条件を変えて結果を比較する
- まず正常なディレクトリで実行して、ファイル数が2かどうかを確認します。
- sample files に gamma.txt を一つ追加して、再度実行して3になるか確認します。
- 追加したファイルのみを削除して、再度2に戻るか確認します。
- 空のディレクトリでは files=0、存在しないパスではエラーと終了状態1かを確認します。
Windowsで編集した後、bash\rや^Mに関連するエラーが出た場合は、改行形式を確認します。実行権限がなくてもbash ファイル名として読み込んで実行できますが、./ファイル名は実行権限と正しいシバンが必要です。実習ファイルを整理する前に現在のパスを確認し、システムディレクトリで一括削除コマンドを実行しないでください。
Windowsのエディタで改行表示がCRLFの場合はLFに変えて保存します。VS CodeではステータスバーでCRLF → LFを選択できます。GNU sedがある実習環境では、以下のように元のファイルをコピーして変換することもできます。対象はこの実習で作成したlist-files.shのみ指定してください。
cp -i -- list-files.sh list-files.sh.crlf-backup
sed -i 's/\r$//' list-files.sh
bash -n list-files.sh
bash list-files.sh "sample files"
構文確認は出力なしでステータス0、元の例のファイルが2つだけであれば、実行結果はfiles=2が予想されます。元に戻すときはバックアップの内容が正しいか確認し、cp -i -- list-files.sh.crlf-backup list-files.shで復元します。




