期待する
find . -delete
現在のディレクトリを削除しますが、削除しません。何故なの?
find . -print
。
cd ..; rm -r dir
、非常に明確なセマンティクスを持つ別のシェル...と
期待する
find . -delete
現在のディレクトリを削除しますが、削除しません。何故なの?
find . -print
。
cd ..; rm -r dir
、非常に明確なセマンティクスを持つ別のシェル...と
回答:
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を使用する必要があります。
「..」ディレクトリは最初のエントリ「..」を読み取り、永久にループします。
結論:
GNU は* BSDのユーティリティfindutils
との互換性を試みfind
ます。
find
* BSDのユーティリティは、rmdir
ドット/ドット-ドットが許可されないPOSIX準拠のC関数を内部的に使用します。
rmdir
ドット/ドット-ドットを許可しない理由は、ファイルシステムの循環パスを防ぐためです。
K&Rによって書かれたCプログラミング言語は、ドット/ドット-ドットが永久ループプログラムにつながる方法の例を示しています。
あなたのためfind
コマンドが返す.
結果として。の情報ページからrm
:
最後のファイル名コンポーネントが「。」であるファイルを削除しようとする試み または、POSIXで義務付けられているように、「..」はプロンプトなしで拒否されます。
そのため、find
この場合はPOSIXルールに固執しているように見えます。
/var/log
あり、それをルートとして実行した場合、すべてのサブディレクトリを削除し、現在のディレクトリも削除すると考えてください。
man
ページに記載find
されています。エラーが出力されないのはなぜですか?
mkdir foo && cd foo && rmdir $(pwd)
。削除する.
(または..
)が機能しません。
引数パスの最後のコンポーネントがの場合、rmdirシステムコールはEINVALで失敗し"."
ます。これはhttp://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.htmlに文書
化されており、動作の理論的根拠は次のとおりです。
パス名/ dotを削除する意味は不明です。これは、特にディレクトリへの複数のリンクがある場合、削除する親ディレクトリ内のファイル(ディレクトリ)の名前が明確ではないためです。
林果皞とトーマスはすでにこれについて良い答えを出しましたが、彼らの答えは、なぜこの振る舞いがそもそも実装されたのかを説明するのを忘れていたように思います。
あなたの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
現在のディレクトリを削除しないことは、単に優れたインターフェイス設計であり、最小限の驚きの原則に準拠しています。