[bash] あるディレクトリがマウントされているかを再帰的に検索してdfコマンドの結果を表示する


ゴールデンウィークはシェルスクリプト漬けな (っ´∀`)っ ゃー でした。こんにちは。さて、Linuxマシンに外付けディスクをマウントして運用している場合、あるディレクトリがマウントされているかどうかの真偽を条件分岐したいこともあるかと思います。

絶対に「/PATH/TO/HOGE」しかマウントしないんだ!という鉄の意志があればよいのですが、皆さんカジュアルにいろんなディレクトリをマウントすると思いますので、チェックが大変ですよね?私は以下のように、${HOME}/Document 以下をUSBハードディスクに外出ししていいます。

[nullpopopo@U24E-RED ~]$ df -h
Filesystem               Size  Used Avail Use% Mounted on
devtmpfs                 3.9G     0  3.9G   0% /dev
tmpfs                    3.9G     0  3.9G   0% /dev/shm
tmpfs                    3.9G  1.5M  3.9G   1% /run
tmpfs                    3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/mapper/fedora-root  678G   37G  607G   6% /
tmpfs                    3.9G   56K  3.9G   1% /tmp
/dev/sda1                976M  211M  698M  24% /boot
/dev/md0                 3.7T  774G  2.9T  21% /home/nullpopopo/Documents
tmpfs                    789M   20K  789M   1% /run/user/42
tmpfs                    789M     0  789M   0% /run/user/1000

■ スクリプト解説

UNIX Linuxのディレクトリはツリー状の階層構造になっているので、深い階層から順に検索し、そのものズバリのディレクトリがマウントされていたら処理を中断して正常終了させ、そうでなければ1つ上野男・・・もとい、1つ上の階層のディレクトリがマウントされているかを検索します。最終的に / ディレクトリが最上位ディレクトリとなるので、ここでおしまいです。まずはコードを見てみましょう。ここでは、サンプルコードを SAMPLE_DF と命名して保存しています。

[nullpopopo@U24E-RED ~]$ cat ${HOME}/bin/SAMPLE_DF
#!/bin/bash
LANG=C

DIR=/home/nullpopopo/Documents/8000_ISOS

# DIR変数に格納されたディレクトリが独立したパーティションとしてマウントされているか、再帰的に検索する。
# マウントされていたことをegrepで検知したら PROC1-1 の処理で正常終了して終わる。
# マウントされていなければ、 PROC1-2 の処理でDIR変数を上位ディレクトリに上書きする。
# 最終的に / の手前のディレクトリまで再帰的に PROC1-2 でDIR変数を上書きする。
# どれにもマッチしなければ、 PROC2 の処理に入り、ループを抜ける。
while :
do
#PROC0
if [ ! / = ${DIR} ];
then
  # PROC1
  if [ 0 = $(df -h | egrep "${DIR}$" > /dev/null ; echo $?) ];
  then
    # PROC1-1
    echo OK ${DIR}
    df -h | egrep "${DIR}$"
    break
  else
    # PROC1-2
    echo NG ${DIR}
    DIR=$(dirname ${DIR})
  fi
else
  # PROC2
  df -h | egrep /$
  break
fi
done

最初にループの外で検索対象のディレクトリをDIR変数へ格納しています。もし、検索したいディレクトリを都度変更したいなら、 DIR=$1 みたいに、シェルスクリプトの引数からDIR変数へ格納してもよいでしょう。

while ループの中身を順に解説すると、まず「if [ ! / = ${DIR} ]; 」(コメント「PROC0」)のif文で DIR変数の中身が / でなければ、コメント「PROC1」の処理にすすみます。

PROC1の処理(if文)では何をしているかというと、条件式に「もしdfコマンドの出力結果に検索対象のディレクトリが含まれていれば」という条件を与えています。条件の真偽はdfコマンドの出力をegrepで検索したときの終了ステータスで判別し、検索対象のディレクトリが含まれていれば、コメント「PROC1-1」の処理を、そうでなければコメント「PROC1-2」の処理に分岐します。

PROC1-1の処理は、単純にDIR変数を表示しつつdfコマンドの結果から検索対象のディレクトリがマウントされている行を抜き出し、breakでwhileのループを抜けます。

PROC1-2の処理は、検索対象のディレクトリがマウントされていないことをechoで表示し、DIR変数を最初のDIR変数の上位ディレクトリで上書きしています。なお、ここでbreakしてしまうと再帰的な検索にならないので、ここではbreakを入れないようにしましょう。

最終的に検索対象のディレクトリが見つからなければ、 / ディレクトリが最上位ディレクトリなので、コメント「PROC0」ののif文で真偽判定が偽となり、コメント「PROC2」の処理に入ります。 / ディレクトリより上位のディレクトリはないので、ここで break を入れて while ループから抜け出します。


■ スクリプト実行結果

それでは、サンプルコード SAMPLE_DF に実行権をつけて実行してみましょう。

[nullpopopo@U24E-RED ~]$ SAMPLE_DF
NG /home/nullpopopo/Documents/8000_ISOS
OK /home/nullpopopo/Documents
/dev/md0                 3.7T  774G  2.9T  21% /home/nullpopopo/Documents

このように、最初にDIR変数へ与えた引数 /home/nullpopopo/Documents/8000_ISOS はマウントされていませんでしたが、DIR変数が1つ上位のディレクトリ /home/nullpopopo/Documents で上書きされ、ここが外付けディスクにマウントされていたので、ここで while ループを抜けて正常終了しています。

それでは、わざと存在しないディレクトリ「/PATH/TO/HOGE」をDIR変数に格納して再度実行してみましょう。

[nullpopopo@U24E-RED ~]$ SAMPLE_DF
NG /PATH/TO/HOGE
NG /PATH/TO
NG /PATH
/dev/mapper/fedora-root  678G   37G  607G   6% /

このように、1つ1つ上位のディレクトリがマウントされているか検索し、 /PATH ディレクトリもないとなると、最終的に残りは / ディレクトリしかないので、 df コマンドの結果からマウントポイント / の行を表示して正常終了しています。


■ まとめ

ループの条件が決まりきっている(自分で決めている)for文のループと違ってwhile文はつい無限ループさせてしまいがちですが、次のようなコーディングルールを決めてスクリプト化してもよいでしょう。

  1. whileの条件式は true 固定
    1. ここで複雑な条件式を書くのは正直つらい
    2. 条件式は do 〜 done の中に書いちゃう
  2. 条件式(真偽判定)の書式
    1. whileループのdo〜doneの間にif文で書く
    2. 条件に合致したらbreakする

ね、簡単でしょう?