このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

便利な厄介者: ディレクトリを指すシンボリックリンク

先日、コマンドラインからの誤操作でファイルをゴッソリ消してしまいました。再生可能なファイルだったので深刻な事態じゃなかったですけど冷や汗が出ましたね。事故の原因は、ディレクトリを指すシンボリックリンクの扱いを勘違いしたことです。

僕の場合、bash, cmd.exe, PowerShellの3つのシェル(と関連コマンド)を使っていて、さらにbashは、LinuxMinGW/MSYSでは挙動が多少違うので、4つの環境のコマンドラインを相手にしてます。なかでも、ディレクトリを指すシンボリックリンクは環境ごとの違いが大きいので厄介です。

間違うと被害が大きい「ディレクトリを指すシンボリックリンクの削除」について記しておきます。

内容:

事実はこうなっている

次のディレクトリ構造を仮定します。

+---dir/
|   |
|   +---subdir@ --> ../dir2
|
+---dir2/
    |   a.txt
    |
    +---subdir2/
            b.txt

dir/subdir は ../dir2/ を指すシンボリックリンクです。dir/ に降りて削除コマンドを実行するとして、その結果は次のようになります。実験用のスクリプトは、https://github.com/m-hiyama/symlink-test に置いてあります。それぞれの場合の注意などは、次の節以降に述べます。

bashと/bin/rm
コマンド リンク自体 リンク先ディレクト リンク先内ファイル リンク先下ブディレクト 確認 エラー 間違ったら
rm subdir 消える - - - 別にいいや
rm -r subdir 消える - - - 別にいいや
rm subdir/ - - - - エラー
rm -r subdir/ - - 消える 消える 一部エラー ウギャー
rm subdir/* - - 消える - 一部エラー ウギャー
rm -r subdir/* - - 消える 消える ウギャー
rmdir subdir - - - - エラー
rmdir subdir/ - - - - エラー

rm -r subdir/ のエラーは「シンボリックリンクを消せない」というものです。

cmd.exeとdel
コマンド リンク自体 リンク先ディレクト リンク先内ファイル リンク先下ブディレクト 確認 エラー 間違ったら
del subdir - - 消える - あり えっ?
del /s subdir - - 消える ファイルは消える あり えっ?
del subdir\ - - 消える - あり えっ?
del /s subdir\ - - 消える ファイルは消える あり えっ?
del subdir\* - - 消える - あり えっ?
del /s subdir\* - - 消える ファイルは消える あり えっ?
rmdir subdir 消える - - - -
rmdir subdir\ 消える - - - -
rmdir /s subdir 消える - - - -
rmdir /s subdir\ 消える - 消える 消える あり ウギャー

rmdir /s subdir\ が「ウギャー」である理由は後で説明します。

PowerShellとrm(Remove-Item)
コマンド リンク自体 リンク先ディレクト リンク先内ファイル リンク先下ブディレクト 確認 エラー 間違ったら
rm -fo subdir 消える - 消える 消える あり えっ?
rm -fo -r subdir 消える - 消える 消える - ウギャー
rm -fo subdir\ 消える - 消える 消える あり えっ?
rm -fo -r subdir\ 消える - 消える 消える - ウギャー
rm -fo subdir\* - - 消える 消える あり えっ?
rm -fo -r subdir\* - - 消える 消える - ウギャー
rmdir -fo subdir 消える - 消える 消える あり えっ?
rmdir -fo subdir\ 消える - 消える 消える あり えっ?
rmdir -fo -r subdir 消える - 消える 消える - ウギャー
rmdir -fo -r subdir\ 消える - 消える 消える - ウギャー

bashと/bin/rm

ファイルと(本物の)ディレクトリに関しては、次の原則があります。

  1. ファイルはrmで消す。(-rオプションなしの)rmdirでファイルは消えない。
  2. ディレクトリはrmdirで消す。rmでディレクトリは消えない。
  3. rmdirで、空でないディレクトリは消せない。
  4. ディレクトリを示す名前は、末尾にスラッシュを付けても付けなくても同じ。

ディレクトリを指すシンボリックリンクでは、本物のディレクトリとは挙動が変わります。

まず、ワイルドカード(グロブ)展開はシェルが行うので、rm subdir/*rm -r subdir/* は、ワイルドカード展開後の形で考えることにして除外していいでしょう。

ディレクトリを指すシンボリックリンクは、ディレクトリに見えますが、消す時はファイルとして扱います。つまり、

ちょっと怖いのは、シンボリックリンクでは「末尾にスラッシュを付けても付けなくても同じ」にならないことです。rm -r subdirたまたま単一のリンクしか消えないのですが、スラッシュを足した rm -r subdir/ は「ウギャー」となります。

[追記]subdirがディレクトリを指すシンボリックリンクのとき、subdir はシンボリックリンクそのものを意味し、subdir/ だとリンク先の本物のディレクトリを意味するようです。rm subdirrm subdir/ の挙動の違いはこの解釈で説明できます。[/追記]

rmは(オプションなしでは)警告を一切しないので、コマンドを打ってしまうとアウトです。-rオプションはどんな時でも要注意です。何を消すべきかを確実に把握してないときは-rを使うべきではありません。なお、rmdirには-rオプションがありません。

cmd.exeとdel

Windowsでのシンボリックリンクは、cmd.exeの内部コマンドであるmklinkで作ります。リンクの消し方は「bashと/bin/rm」とは違います。

delはファイルを消すコマンドですが、del subdir はエラーにはなりません。delに対して指定されたディレクトリ名subdirは、subdir/* と解釈されます。

/sオプションは、/bin/rmの-rオプションと同様に危険です。しかし、複数のファイルが一度に消える可能性があるとき、delは常に警告します。その点で、ほんとに「ウギャー」となることは少ないでしょう。

しかし落とし穴もあります。それは、rmdir /s subdirrmdir /s subdir\ の挙動の違いです。僕は、バックスラッシュのあるなしは動作に影響を与えないだろうと思ってました。rmdir /s subdir がリンクを消すだけなので、rmdir /s subdir\ も危険はないだろうと思うと大間違い。確認はあるのですが、rmdir /s の確認は一回だけなので、無意識に[Enter]キーを一度叩くと、ディレクトリツリーをガッツリやられます。

バックスラッシュのあるなしで挙動が変わるのはこの例だけです。

  • del subdirdel subdir\
  • del /s subdirdel /s subdir\
  • rmdir subdirrmdir subdir\
  • rmdir /s subdir rmdir /s subdir\要注意

-rオプションでも/sオプションでも、例外的ラッキー事象を覚えたりしないで、常にサブツリーごと逝かれると心得ておくほうがいいですね。

PowerShellとrm(Remove-Item)

次はPowerShellですが、シンボリックリンクに対してはオプションなしでは次のエラーとなります。

rmdir : C:\Users\hiyama\Work\tmp\symlink-test\dir\subdir\ は NTFS 接合ポイントです。
このオブジェクトを削除または変更するには、Force パラメーターを使用してください。

それで、-fo (-Force)オプションを付けてます。

ともかく何をやっても全てのファイルが消えます。しかし、警告は出ます。-rを付けると黙って全ファイル消去。

bashの場合と同様に、-rは覚悟して使え、ってことです。

MinGW/MSYSの場合

MinGW/MSYSでは、シンボリックリンクを作ることも認識することも出来ないようです。lnコマンドはありますが、-s(シンボリックリンク)オプションを付けてもファイルコピーをします。

ls -F の表示を見るに、Windowsシンボリックリンクを認識できないで、本物のディレクトリだと思っているようです。

Windowsレベルのディレクトリへのシンボリックリンクは、MinGW/MSYSからはほぼ本物のディレクトリのように振る舞いますが、rmdir subdir で、空でないディレクトリでも消えます。subdirの中身(指している先の中身)は消えませんから「ウギャー」にはなりません。

まとめ

ディレクトリを指すシンボリックリンクは、ディレクトリとファイルの中間のような存在で、そもそも理解し難いものです。そしてさらに、プラットフォームによって扱いが異なる、と。なんとも厄介ですよね、とても便利ですけど。

いずれのプラットフォームでも、-r, /s のオプションはホントに要注意です。対話的な利用では、警告が出たり、直後の中断とかで災厄を避けられることもありますが、心底恐ろしいのは、シェルスクリプトからの削除コマンドです。他のプラットフォームのスクリプト機械的に修正したら、挙動が違っていた、なんて事態にならないようにしないと。

関連記事: