I would expect
find . -delete
to delete the current directory, but it doesn't. Why not?
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communityThe members of findutils
aware of it, it's for compatible with *BSD:
One of the reasons that we skip deletion of "." is for compatibility with *BSD, where this action originated.
The NEWS in findutils source code shows that they decided to keep the behavior:
#20802: If -delete fails, find's exit status will now be non-zero. However, find still skips trying to delete ".".
[UPDATE]
Since this question become one of the hot topic, so i dive into FreeBSD source code and come out a more convincing reason.
Let's see the find utility source code of FreeBSD:
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);
}
...
As you can see, if it doesn't filter out dot and dot-dot, then it will reach rmdir()
C function defined by POSIX's unistd.h
.
Do a simple test, rmdir with dot/dot-dot argument will return -1:
printf("%d\n", rmdir(".."));
Let's take a look how POSIX describe rmdir:
If the path argument refers to a path whose final component is either dot or dot-dot, rmdir() shall fail.
No reason was given why shall fail
.
I found rename
explain some reason:
Renaming dot or dot-dot is prohibited in order to prevent cyclical file system paths.
Cyclical file system paths ?
I look over The C Programming Language (2nd Edition) and search for directory topic, surprisingly i found the code is similar:
if(strcmp(dp->name,".") == 0 || strcmp(dp->name,"..") == 0)
continue;
And the comment !
Each directory always contains entries for itself, called ".", and its parent, ".."; these must be skipped, or the program will loop forever.
"loop forever", this is same like how rename
describe it as "cyclical file system paths" above.
I slightly modify the code and to make it run in Kali Linux based on this answer:
#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);
}
Let's see:
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$
It work correctly, now what if I comment out the continue
instruction:
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$
As you can see, I have to use Ctrl+C to kill this infinitely loop program.
The '..' directory read its first entry '..' and loop forever.
Conclusion:
GNU findutils
try to compatible with find
utility in *BSD.
find
utility in *BSD internally use rmdir
POSIX-compliant C function which dot/dot-dot is not allow.
The reason of rmdir
do not allow dot/dot-dot is prevent cyclical file system paths.
The C Programming Language written by K&R shows the example of how dot/dot-dot will lead to forever loop program.
Because your find
command returns .
as result. From the info page of rm
:
Any attempt to remove a file whose last file name component is ‘.’ or ‘..’ is rejected without any prompting, as mandated by POSIX.
So, it looks like find
just sticks to POSIX rules in this case.
/var/log
and you ran that as root, thinking that it'd remove all the subdirs and it removed the current directory as well?
Nov 18, 2016 at 15:39
man
page for find
says: "If the removal failed, an error message is issued." Why is no error printed?
mkdir foo && cd foo && rmdir $(pwd)
. It's removing .
(or ..
) that doesn't work.
Nov 24, 2016 at 21:27
The rmdir system call fails with EINVAL if the last component of its argument path is "."
. It's documented at http://pubs.opengroup.org/onlinepubs/009695399/functions/rmdir.html
and the rationale for the behavior is:
The meaning of deleting pathname /dot is unclear, because the name of the file (directory) in the parent directory to be removed is not clear, particularly in the presence of multiple links to a directory.
Calling rmdir(".")
as a system call didn't work when I tried it, so no higher level tool can succeed.
You must delete the directory through its real name not its .
alias.
While 林果皞 and Thomas already gave good answers on this, I feel that their answers forgot to explain why this behaviour was implemented in the first place.
In your find . -delete
example deleting the current directory sounds pretty logical and sane. But consider:
$ find . -name marti\*
./martin
./martin.jpg
[..]
Does deleting .
still sound logical and sane to you?
Deleting a non-empty directory is an error – so you're unlikely to lose data with this with find
(although you could with rm -r
) – but your shell will have its current working directory set to a directory that no longer exist, leading to some confusing and surprising behaviour:
$ pwd
/home/martin/test
$ rm -r ../test
$ touch foo
touch: cannot touch 'foo': No such file or directory
Not deleting the current directory is simply good interface design and complies with the principle of least surprise.
find . -print
.cd ..; rm -r dir
with another shell with quite clear semantics...