Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
whichコマンドの内容理解(シェルスクリプト)
#! /bin/sh
# シェルのオプションの設定
# `-e` : コマンドが0以外のステータスで終了した場合即座に終了(例外あり)
# `-f` : パス名のワイルドカード展開を無効
# http://itpro.nikkeibp.co.jp/article/COLUMN/20060227/230881/
set -ef
# `$KSH_VERSION` が確認できたらkshなので、それ用の `puts` を定義する
# `printf` で引数を表示したあと改行するようにしている
# kshでは `print -r` でエスケープを無視してそのまま出力する
# kshの `print` はデフォルトで改行する
# https://orebibou.com/2014/11/%E7%8F%BE%E5%9C%A8%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8Bksh%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%A2%BA%E8%AA%8D/
# http://www.geocities.jp/taka_owl2005/job/UNIX/tool/ksh.html#print
if test -n "$KSH_VERSION"; then
puts() {
print -r -- "$*"
}
else
puts() {
printf '%s\n' "$*"
}
fi
ALLMATCHES=0
# オプションの取得
# `getopts` により `$ which -a TARGET` のときは `$whichopts` に `a` が入り、
# "マッチした全ての候補を表示する"フラグを立てる
# それ以外(何もないか、a以外のオプション)のときは `$whichopts` に `?` が入る
# `while` で繰り返すと `-abcd` などを全て見ることができる
# http://linux.just4fun.biz/?%E9%80%86%E5%BC%95%E3%81%8D%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88/getopts%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E5%BC%95%E6%95%B0%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%28bash%E3%83%93%E3%83%AB%E3%83%89%E3%82%A4%E3%83%B3%29
# http://takuya-1st.hatenablog.jp/entry/2015/12/24/234238
while getopts a whichopts
do
case "$whichopts" in
a) ALLMATCHES=1 ;;
?) puts "Usage: $0 [-a] args"; exit 2 ;;
esac
done
# `$OPTIND` は `getopts` でセットされる変数で、
# プログラム名からオプションまでの個数が入る(よって最低でも1)
# オプションより後ろの引数は数えられていない
# 一方 `$*` にはプログラム名を含まない引数のみ入っているので、
# `-1` することでオプションだけシフトできるよう数を合わせている
# つまり引数リストからオプションを取り除き、
# 検索対象だけにする
# http://shellscript.sunone.me/parameter.html
shift $(($OPTIND - 1))
# `$#` は引数( `$*` )の数がセットされた変数
# 残っている引数が0個、つまり検索対象が指定されていないとき、
# このプログラムが返す終了ステータスを1にセットする
# http://shellscript.sunone.me/variable.html#%E7%89%B9%E6%AE%8A%E5%A4%89%E6%95%B0%E4%B8%80%E8%A6%A7%E8%A1%A8
if [ "$#" -eq 0 ]; then
ALLRET=1
else
ALLRET=0
fi
# 環境変数 `$PATH` の末尾に `:` がついている場合、
# システム全体の検索パスがその後ろに追加される
# `::` と2つつけたときはその2コロン間にシステム全体の検索パスが追加される
# ここでは末尾に `:` があった場合に `::` にしている
# 意図が不明……
# https://qiita.com/mollifier/items/2dc274244ac698bb943b
case $PATH in
(*[!:]:) PATH="$PATH:" ;;
esac
# `$@` は配列化された引数リスト
# つまり、指定された引数それぞれについて
# 引数がないときは何もせずに抜ける
for PROGRAM in "$@"; do
# 以下の処理が終わるまでに0に書き換えられなかったら失敗
RET=1
# Internal Field Separator `$IFS` の中身の退避
# `$IFS` は単語の区切りとなる文字がセットされている環境変数
# https://qiita.com/kawaz/items/00a4b1693bf4cf67e8ca
IFS_SAVE="$IFS"
# `$IFS` を `:` に設定
# 後述の `$PATH` の走査のため
IFS=:
# 指定に `/` が含まれていて、それが通常ファイルかつ実行可能なら
# それをそのまま出力するだけ
# それ以外なら
# `$PATH` に何も入っていないとき、パスとして `$ELEMENT` にはカレントを入れておく
# `$PATH` のある場所に指定のファイルが通常ファイルかつ実行可能で存在したら表示
# "マッチした全ての候補を表示する"フラグが立っていなかったら、
# 1つ見つけた時点で即座に `for` ループを出る
# いずれでも1つでも見つかったら `$RET` を0にして成功を示しておく
# 走査が終了したら `$IFS` を元に戻す
# 失敗( `$RET` が0でない)ならこのプログラムが返す終了ステータスを1にセットする
case $PROGRAM in
*/*)
if [ -f "$PROGRAM" ] && [ -x "$PROGRAM" ]; then
puts "$PROGRAM"
RET=0
fi
;;
*)
for ELEMENT in $PATH; do
if [ -z "$ELEMENT" ]; then
ELEMENT=.
fi
if [ -f "$ELEMENT/$PROGRAM" ] && [ -x "$ELEMENT/$PROGRAM" ]; then
puts "$ELEMENT/$PROGRAM"
RET=0
[ "$ALLMATCHES" -eq 1 ] || break
fi
done
;;
esac
IFS="$IFS_SAVE"
if [ "$RET" -ne 0 ]; then
ALLRET=1
fi
done
# 上までで指定した、このプログラムが返す終了ステータスで、終了する
exit "$ALLRET"
# Environment
# $ uname -a
# Linux **** 4.4.0-104-generic #127-Ubuntu SMP Mon Dec 11 12:16:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
# $ cat /etc/os-release
# NAME="Ubuntu"
# VERSION="16.04.3 LTS (Xenial Xerus)"
# ID=ubuntu
# ID_LIKE=debian
# PRETTY_NAME="Ubuntu 16.04.3 LTS"
# VERSION_ID="16.04"
# HOME_URL="http://www.ubuntu.com/"
# SUPPORT_URL="http://help.ubuntu.com/"
# BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
# VERSION_CODENAME=xenial
# UBUNTU_CODENAME=xenial
# References
# http://itpro.nikkeibp.co.jp/article/COLUMN/20060227/230881/
# https://orebibou.com/2014/11/%E7%8F%BE%E5%9C%A8%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8Bksh%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%A2%BA%E8%AA%8D/
# http://linux.just4fun.biz/?%E9%80%86%E5%BC%95%E3%81%8D%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88/getopts%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%A6%E5%BC%95%E6%95%B0%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%28bash%E3%83%93%E3%83%AB%E3%83%89%E3%82%A4%E3%83%B3%29
# http://takuya-1st.hatenablog.jp/entry/2015/12/24/234238
# http://shellscript.sunone.me/parameter.html
# http://shellscript.sunone.me/variable.html#%E7%89%B9%E6%AE%8A%E5%A4%89%E6%95%B0%E4%B8%80%E8%A6%A7%E8%A1%A8
# https://qiita.com/mollifier/items/2dc274244ac698bb943b
# https://qiita.com/kawaz/items/00a4b1693bf4cf67e8ca
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment