LoginSignup
38
34

More than 5 years have passed since last update.

え! while readにそんなワナがあったの!?

Last updated at Posted at 2014-10-23

[問題]次のシェルスクリプトの欠陥を全て指摘せよ

問題のシェルスクリプト

rmdeadlink.sh:指定ディレクトリーのデッドリンクを削除するプログラム

  • ここで言う「デッドリンク」とは、実体が無くなってしまったシンボリックリンクを指すものとする。
  • ただし、指定したディレクトリーにどんな名前のファイルがあるかわからない。
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

さて、全部指摘できましたか?

え、まだある!? それ、追記しますから教えてください!!!

38
34
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
34