なぜ見つからない。-delete現在のディレクトリを削除しますか?


22

期待する

find . -delete

現在のディレクトリを削除しますが、削除しません。何故なの?


3
おそらく、現在の作業ディレクトリを削除するのは良い考えではないからです。
アレクセイマグラ

デフォルトの動作などのIが、それは、例えば、と一致しない-合意しましたfind . -print
mbroshi

@AlexejMaguraは同情しますが、現在のディレクトリを削除することと、開いているファイルを削除することとが異なる理由はわかりません。オブジェクトは、オブジェクトへの参照が存在するまで存続し、その後ガベージコレクションが行われます。あなたは行うことができcd ..; rm -r dir、非常に明確なセマンティクスを持つ別のシェル...と
Rmano

@Rmanoこれは本当です:原則として私がやらないことです:ディレクトリを上に移動してから現在のディレクトリを削除するだけです。なぜそんなに大したことなのか完全にはわかりません-相対パスが機能しなくなったなど、現在のディレクトリが存在しないという不幸がありましたが、絶対パスを使用すればいつでも抜け出すことができますが、私の一部は、それは一般的には良い考えではないと言うだけです。
アレクセイマグラ

回答:


29

findutils それ知っているメンバー、それは* BSDとの互換性のためです:

「。」の削除をスキップする理由の1つ このアクションが発生した* BSDとの互換性のためです。

findutilsのソースコードのNEWSは、彼らが動作を維持することを決めたことを示しています。

#20802: If -delete fails, find's exit status will now be non-zero. However, find still skips trying to delete ".".

[更新]

この質問はホットトピックの1つになるので、FreeBSDのソースコードに飛び込み、より説得力のある理由を明らかにします。

FreeBSDのfindユーティリティのソースコードを見てみましょう。

int
f_delete(PLAN *plan __unused, FTSENT *entry)
{
    /* ignore these from fts */
    if (strcmp(entry->fts_accpath, ".") == 0 ||
        strcmp(entry->fts_accpath, "..") == 0)
        return 1;
...
    /* rmdir directories, unlink everything else */
    if (S_ISDIR(entry->fts_statp->st_mode)) {
        if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY)
            warn("-delete: rmdir(%s)", entry->fts_path);
    } else {
        if (unlink(entry->fts_accpath) < 0)
            warn("-delete: unlink(%s)", entry->fts_path);
    }
...

ご覧のとおり、ドットとドットドットを除外しない場合rmdir()、POSIXで定義されたC関数に到達しunistd.hます。

簡単なテストを行うと、ドット/ドット-ドット引数を指定したrmdirは-1を返します。

printf("%d\n", rmdir(".."));

POSIXがrmdirどのように記述している見てみましょう:

path引数が最終コンポーネントがドットまたはドットドットのいずれかであるパスを参照する場合、rmdir()は失敗します。

理由は与えられませんでしたshall fail

私はrename いくつかの理由を説明しました:

循環的なファイルシステムパスを防ぐために、ドットまたはドットドットの名前を変更することは禁止されています。

循環ファイルシステムパス

Cプログラミング言語(第2版)を調べてディレクトリトピックを検索すると、驚くほどコードが似ていることがわかりまし

if(strcmp(dp->name,".") == 0 || strcmp(dp->name,"..") == 0)
    continue;

そしてコメント!

各ディレクトリには、常に「。」と呼ばれる自身のエントリと、その親の「..」が含まれています。これらはスキップする必要があります。そうしないと、プログラムは永久ループします

"loop forever"、これは上記の「循環ファイルシステムパス」renameとして説明する方法と同じです。

コードを少し修正し、この回答に基づいてKali Linuxで実行するようにします

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <dirent.h>
#include <unistd.h>

void fsize(char *);
void dirwalk(char *, void (*fcn)(char *));

int
main(int argc, char **argv) {
    if (argc == 1)
        fsize(".");
    else
        while (--argc > 0) {
            printf("start\n");
            fsize(*++argv);
        }
    return 0;
}

void fsize(char *name) {
    struct stat stbuf;
    if (stat(name, &stbuf) == -1 )  {
        fprintf(stderr, "fsize: can't access %s\n", name);
        return;
    }
    if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
        dirwalk(name, fsize);
    printf("%81d %s\n", stbuf.st_size, name);
}

#define MAX_PATH 1024
void dirwalk(char *dir, void (*fcn)(char *))
{
    char name[MAX_PATH];
    struct dirent *dp;

    DIR *dfd;

    if ((dfd = opendir(dir)) == NULL) {
            fprintf(stderr, "dirwalk: can't open %s\n", dir);
            return;
    }

    while ((dp = readdir(dfd)) != NULL) {
            sleep(1);
            printf("d_name: S%sG\n", dp->d_name);
            if (strcmp(dp->d_name, ".") == 0
                            || strcmp(dp->d_name, "..") == 0) {
                    printf("hole dot\n");
                    continue;
                    }
            if (strlen(dir)+strlen(dp->d_name)+2 > sizeof(name)) {
                    printf("mocha\n");
                    fprintf(stderr, "dirwalk: name %s/%s too long\n",
                                    dir, dp->d_name);
                    }
            else {
                    printf("ice\n");
                    (*fcn)(dp->d_name);
            }
    }
    closedir(dfd);
}

どれどれ:

xb@dnxb:/test/dot$ ls -la
total 8
drwxr-xr-x 2 xiaobai xiaobai 4096 Nov 20 04:14 .
drwxr-xr-x 3 xiaobai xiaobai 4096 Nov 20 04:14 ..
xb@dnxb:/test/dot$ 
xb@dnxb:/test/dot$ cc /tmp/kr/fsize.c -o /tmp/kr/a.out 
xb@dnxb:/test/dot$ /tmp/kr/a.out .                     
start
d_name: S..G
hole dot
d_name: S.G
hole dot
                                                                             4096 .
xb@dnxb:/test/dot$ 

continue命令をコメントアウトするとどうなるでしょうか。

xb@dnxb:/test/dot$ cc /tmp/kr/fsize.c -o /tmp/kr/a.out 
xb@dnxb:/test/dot$ /tmp/kr/a.out .
start
d_name: S..G
hole dot
ice
d_name: S..G
hole dot
ice
d_name: S..G
hole dot
ice
^C
xb@dnxb:/test/dot$

ご覧のとおり、この無限ループプログラムを強制終了するには、Ctrl+ Cを使用する必要があります。

「..」ディレクトリは最初のエントリ「..」を読み取り、永久にループします。

結論:

  1. GNU は* BSDのユーティリティfindutilsとの互換性を試みfindます。

  2. find* BSDのユーティリティは、rmdirドット/ドット-ドットが許可されないPOSIX準拠のC関数を内部的に使用します。

  3. rmdirドット/ドット-ドットを許可しない理由は、ファイルシステムの循環パスを防ぐためです。

  4. K&Rによって書かれたCプログラミング言語は、ドット/ドット-ドットが永久ループプログラムにつながる方法の例を示しています。


16

あなたのためfindコマンドが返す.結果として。の情報ページからrm

最後のファイル名コンポーネントが「。」であるファイルを削除しようとする試み または、POSIXで義務付けられているように、「..」はプロンプトなしで拒否されます。

そのため、findこの場合はPOSIXルールに固執しているように見えます。


2
当然のことながら、POSIXは重要ですが、現在のディレクトリを削除すると、親アプリケーションとそうでないものによっては、非常に大きな問題が発生する可能性があります。現在のディレクトリが/var/logあり、それをルートとして実行した場合、すべてのサブディレクトリを削除し、現在のディレクトリも削除すると考えてください。
アレクセイマグラ

1
これは良い理論ですが、「削除に失敗した場合はエラーメッセージが表示されます」というmanページに記載findされています。エラーが出力されないのはなぜですか?
mbroshi

1
@AlexejMagura一般に、現在のディレクトリを削除すると正常に機能しますmkdir foo && cd foo && rmdir $(pwd)。削除する.(または..)が機能しません。
Tavianバーンズ

4

引数パスの最後のコンポーネントがの場合、rmdirシステムコールはEINVALで失敗し"."ます。これはhttp://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.htmlに文書 化されており、動作の理論的根拠は次のとおりです。

パス名/ dotを削除する意味は不明です。これは、特にディレクトリへの複数のリンクがある場合、削除する親ディレクトリ内のファイル(ディレクトリ)の名前が明確ではないためです。


2

呼び出しrmdir(".")私はそれをしようとしたときにシステムコールはそれほど高くないレベルのツールが成功することができ、動作しませんでしたと。

.エイリアスではなく、実際の名前でディレクトリを削除する必要があります。


1

林果皞とトーマスはすでにこれについて良い答えを出しましたが、彼らの答えは、なぜこの振る舞いがそもそも実装されたのを説明するのを忘れていたように思います。

あなたのfind . -delete例では、現在のディレクトリを削除することはかなり論理的で健全です。しかし、考慮してください:

$ find . -name marti\*
./martin
./martin.jpg
[..]

削除は.まだ論理的で正気に聞こえますか?

これに失うデータにしているそうあなたがそう-非空のディレクトリを削除するとエラーになりますfind(あなたができるがrm -r) -しかし、あなたのシェルは、いくつかの混乱につながる、もはや存在していることのディレクトリに、現在の作業ディレクトリを設定しているだろうそして驚くべき行動:

$ pwd
/home/martin/test
$ rm -r ../test 
$ touch foo
touch: cannot touch 'foo': No such file or directory

現在のディレクトリを削除しないことは、単に優れたインターフェイス設計であり、最小限の驚きの原則に準拠しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.