先日、コマンドラインからの誤操作でファイルをゴッソリ消してしまいました。再生可能なファイルだったので深刻な事態じゃなかったですけど冷や汗が出ましたね。事故の原因は、ディレクトリを指すシンボリックリンクの扱いを勘違いしたことです。
僕の場合、bash, cmd.exe, PowerShellの3つのシェル(と関連コマンド)を使っていて、さらにbashは、LinuxとMinGW/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
ファイルと(本物の)ディレクトリに関しては、次の原則があります。
- ファイルはrmで消す。(-rオプションなしの)rmdirでファイルは消えない。
- ディレクトリはrmdirで消す。rmでディレクトリは消えない。
- rmdirで、空でないディレクトリは消せない。
- ディレクトリを示す名前は、末尾にスラッシュを付けても付けなくても同じ。
ディレクトリを指すシンボリックリンクでは、本物のディレクトリとは挙動が変わります。
まず、ワイルドカード(グロブ)展開はシェルが行うので、rm subdir/*
と rm -r subdir/*
は、ワイルドカード展開後の形で考えることにして除外していいでしょう。
ディレクトリを指すシンボリックリンクは、ディレクトリに見えますが、消す時はファイルとして扱います。つまり、
ちょっと怖いのは、シンボリックリンクでは「末尾にスラッシュを付けても付けなくても同じ」にならないことです。rm -r subdir
はたまたま単一のリンクしか消えないのですが、スラッシュを足した rm -r subdir/
は「ウギャー」となります。
[追記]subdirがディレクトリを指すシンボリックリンクのとき、subdir はシンボリックリンクそのものを意味し、subdir/ だとリンク先の本物のディレクトリを意味するようです。rm subdir
と rm 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 subdir
と rmdir /s subdir\
の挙動の違いです。僕は、バックスラッシュのあるなしは動作に影響を与えないだろうと思ってました。rmdir /s subdir
がリンクを消すだけなので、rmdir /s subdir\
も危険はないだろうと思うと大間違い。確認はあるのですが、rmdir /s の確認は一回だけなので、無意識に[Enter]キーを一度叩くと、ディレクトリツリーをガッツリやられます。
バックスラッシュのあるなしで挙動が変わるのはこの例だけです。
del subdir
=del subdir\
del /s subdir
=del /s subdir\
rmdir subdir
=rmdir 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 のオプションはホントに要注意です。対話的な利用では、警告が出たり、直後の中断とかで災厄を避けられることもありますが、心底恐ろしいのは、シェルスクリプトからの削除コマンドです。他のプラットフォームのスクリプトを機械的に修正したら、挙動が違っていた、なんて事態にならないようにしないと。
関連記事: