今回やること
シェルスクリプトは、ターミナルで実行する複数の処理をファイルへまとめたものです。
この記事ではBashを使い、受け取ったパスがファイル、ディレクトリ、未検出のどれかを表示するinspect-paths.shを1本作ります。
ifで引数の有無を確認するforで複数の引数を順番に調べる"$@"で空白を含む引数を壊さず扱う- BashとShellCheckで検査する
ファイルの作成・削除やシステム設定の変更は行わない、読み取り専用の練習です。
前提条件
- macOS、Linux、WSL、Git BashなどでBashが使える
- ターミナルの基本操作ができる
PowerShellは別の言語です。Windowsでこの記事を試す場合は、WSLまたはGit Bashを使ってください。
bash --version
バージョン番号が表示されれば準備完了です。
1. スクリプトを作る
学習用フォルダーへ移動し、inspect-paths.shを作ります。
mkdir bash-path-practice
cd bash-path-practice
エディターで、次の内容を保存してください。
#!/usr/bin/env bash
set -u
if (( $# == 0 )); then
printf '使い方: %s PATH [PATH ...]\n' "$0" >&2
exit 64
fi
found_count=0
for path in "$@"; do
if [[ -f "$path" ]]; then
printf 'file: %s\n' "$path"
((found_count += 1))
elif [[ -d "$path" ]]; then
printf 'directory: %s\n' "$path"
((found_count += 1))
else
printf 'not found: %s\n' "$path" >&2
fi
done
printf 'found: %d\n' "$found_count"
if (( found_count == 0 )); then
exit 1
fi
コードの要点
#!/usr/bin/env bashはシバンと呼ばれ、PATHからBashを探してこのファイルを実行する指定です。
$#は引数の個数です。引数が0個なら使い方を表示し、終了コード64で終わります。
if (( $# == 0 )); then
# 引数がない時の処理
fi
"$@"は、渡された引数をそれぞれ別の値として展開します。ダブルクォートが重要です。
for path in "$@"; do
# 各引数を1つずつ調べる
done
たとえば"path with spaces"は、クォートすることで3語ではなく1つの引数になります。変数を使う時も"$path"とクォートします。
[[ -f "$path" ]]は通常ファイル、[[ -d "$path" ]]はディレクトリかを調べます。
2. Bashで直接実行する
まず、実行権限を付けずにBashへファイルを渡します。
bash inspect-paths.sh . inspect-paths.sh "path with spaces"
次のような結果になります。
directory: .
file: inspect-paths.sh
not found: path with spaces
found: 2
"path with spaces"が1行で表示されれば、1つの引数として扱えています。
not foundは標準エラー出力へ、それ以外は標準出力へ書くため、利用するターミナルによって表示順が前後する場合があります。
3. 実行可能ファイルにする
chmod +x inspect-paths.sh
./inspect-paths.sh . inspect-paths.sh "path with spaces"
今回は先頭のシバンによりBashが選ばれます。
4. 引数なしと終了コードを確認する
引数を付けずに実行します。
./inspect-paths.sh
printf 'exit: %s\n' "$?"
使い方の後にexit: 64と表示されます。終了コードは、0が成功、0以外が成功以外の状態を表します。
存在しないパスだけを渡した場合も確認します。
./inspect-paths.sh "not found"
printf 'exit: %s\n' "$?"
この場合はexit: 1です。少なくとも1つ見つかった場合は0で終了します。
5. 構文とShellCheckを検査する
Bash自身で、実行せずに構文だけを確認できます。
bash -n inspect-paths.sh
printf 'exit: %s\n' "$?"
何も表示されずexit: 0なら、Bashの構文エラーはありません。
次にShellCheckで、クォート漏れや移植性の問題を静的に検査します。静的解析とは、実行せずにコードを調べることです。
shellcheck inspect-paths.sh
ShellCheckが未導入の場合は、公式Installページから自分のOS向け方法を選んでください。指摘が表示されなければ成功です。
成功確認
次の4点を確認できれば完了です。
bash inspect-paths.sh ...で実行できる./inspect-paths.sh ...でも実行できる- 空白を含む未検出パスが1行で表示される
bash -nとShellCheckで問題が出ない
このスクリプトはパスの種類を読むだけで、対象を変更・削除しません。
よくあるつまずき
Permission deniedと表示される
./inspect-paths.shで実行するには実行権限が必要です。
chmod +x inspect-paths.sh
または、実行権限を付けずにbash inspect-paths.shを使えます。
bad interpreterと表示される
1行目の前に空行がないか、シバンが正しいか確認します。
#!/usr/bin/env bash
Windows形式の改行が原因になる場合は、エディターで改行コードをLFへ変更してください。
空白を含むパスが複数に分かれる
呼び出し側とスクリプト側の両方でクォートします。
./inspect-paths.sh "path with spaces"
for path in "$@"; do
printf '%s\n' "$path"
done
$@や$pathをクォートなしで展開しないでください。
shellcheck: command not foundと表示される
ShellCheckはBash本体とは別のツールです。公式Installページを確認して導入するか、まずbash -nの検査まで進めてください。
練習
elifを1つ追加し、シンボリックリンクをlink:と表示してみましょう。
elif [[ -L "$path" ]]; then
printf 'link: %s\n' "$path"
((found_count += 1))
シンボリックリンクの判定を通常ファイルより先に置く必要があるか、実際に調べて考えてみてください。変更後は必ずbash -nとShellCheckを再実行します。
次のステップ
構文をすぐ確認したい時はBashチートシートを使ってください。次は、終了コードを使って別のコマンドの成功・失敗を判定する小さなスクリプトへ進むと理解が深まります。
ファイル削除、バックアップ、trap、一時ファイル、管理者権限を伴う自動化は、入力検証と失敗時の復旧方法を学んでから別の記事で扱います。