[問題]次のシェルスクリプトの欠陥を全て指摘せよ
問題のシェルスクリプト
rmdeadlink.sh
:指定ディレクトリーのデッドリンクを削除するプログラム
- ここで言う「デッドリンク」とは、実体が無くなってしまったシンボリックリンクを指すものとする。
- ただし、指定したディレクトリーにどんな名前のファイルがあるかわからない。
#! /bin/sh
[ $# -eq 1 ] || {
echo "Usage : ${0##*/} <target_dir>" 1>&2
exit 1
}
dir=$1
cd $dir
ls -1 |
while read file; do
# デッドリンクの場合、"-e"でチェックすると偽が返される
[ -L "$file" ] || continue
[ -e $file ] || rm -f $file
done
Thinking Time
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
[解答]
0. 意図しない場所の同名コマンドが実行される恐れがある
これはコメント欄からのご指摘に基づくものです。
環境変数PATHがいじくられていると同名の予期せぬコマンドが実行される恐れがあります。安全を期すなら、環境変数PATHを/binと/usr/binだけにしましょう。(今回使っているコマンドはいずれもPOSIX範囲内なので、どちらかのディレクトリーに必ずあります)
PATH=/bin:/usr/bin # シェルスクリプトの冒頭にこの行を追加
(「/binや/usr/binにあるコマンドそのものが不正に書き換えられていたら?」という指摘があるかもしれませんが、それはOSそのものが既に正常ではないことを意味し、言いだしたらキリがありませんのでナシにします)
1. 引数$1
がディレクトリーであることを確かめていない
ディレクトリーでない引数を指定するとその後のcdコマンドが誤作動しますので、冒頭のtestコマンド([
)に、引数がディレクトリーとして実在していることを確認するためのコードを追加しましょう。
[ \( $# -eq 1 \) -a \( -d "$1" \) ] || {
echo "Usage : ${0##*/} <target_dir>" 1>&2
exit 1
}
2. 引数$1
がスペースを含んでいると誤動作する
半角スペースを含んでいるような特殊なディレクトリー名だとcdコマンドに、は複数の引数として渡されてしまい、誤動作してしまいます。なので、cdコマンドの引数$dir
はダブルクォーテーションで囲みましょう。
cd "$dir"
3. 引数$1
が-
で始まっていると誤作動する
ディレクトリー名がハイフンで始まっていると、cdコマンドにそれはオプションであると誤解され、誤動作してしまいます。これを防止するためには、絶対ディレクトリーでないと判断した時は、強制的に先頭にカレントディレクトリ―./
を付けるようにしましょう。
具体的には変数dirを代入している行の後ろに次のコードを追加します。
# シェル変数$dirの先頭が/で始まってるならそのまま、
# さもなければ先頭に"./"を付ける。
case "$dir" in
/*) :;;
*) dir="./$dir";;
esac
4. 引数$1
のディレクトリーに移動できない場合の対策が抜けている
いくら引数$1
でディレクトリーが実在していることを確認しても、パーミッションが無い等の理由でそのディレクトリーに移動できなかったら……。そうです、意図せずカレントディレクトリーのデッドリンクを消そうとしてしまいます。なので、cdコマンドの次の行に次のコードを挿入し、ディレクトリー移動に失敗したら、中断するようにしましょう。
[ $? -eq 0 ] || {
echo "${0##*/}: $dir: Permission denied" 1>&2
exit 1
}
5. ファイル名がスペースを含んでいると誤動作する
これも2と同じ理屈です。testコマンドもrmコマンドも誤動作してしまいます。両方ともダブルクォーテーションで囲みましょう。
[ -L "$file" ] || continue
[ -e "$file" ] || rm -f "$file"
6. ファイル名が-
で始まっていると誤作動する
これも3と同じ理屈です。もし、先の5の対策コードも下記に記す対策コードもなかったら、-rf /home/your_homedir
なんていう特殊なリンクファイルが置かれていた日には、大変なことになりますよ!奥さん。
file="./$file"
7. ファイル名が.
で始まっているとそもそも見つからない
.
で始まるファイルは一般的に隠しファイル扱いされます。従って、lsコマンドもデフォルトではそのようなファイルを列挙しません。
列挙させるようにするには、lsコマンドに-a
オプションを追加しなければなりません。
ls -1a
8. それから…
さて、このシェルスクリプトには実はもう一つ欠陥があります。え、「cdでディレクトリー移ってlsなんてせずにfind -type l -depth 1
でいいだろ」って。
ごもっともな意見なんですが、それ以外の明らかな欠陥を含んでいるのですよ。
それは、
:
:
:
:
:
readコマンドがバックスラッシュを特別扱いする仕様であることを忘れている。
です。
試しに次のワンライナーを実行してみてください。
$ echo 'Total price : \500' | while read str; do echo "$str"; done
Total price : 500
$
これでは、バックスラッシュ付のファイルを正しく扱えません。
ではどーすればいいのかというと、readコマンドに-r
オプションを付けるだけです。これで特別扱いはなくなります。
while read -r file; do
さて、全部指摘できましたか?
え、まだある!? それ、追記しますから教えてください!!!