Skip to content

Commit 1adbf33

Browse files
gojKrzysztof Goj
authored andcommitted
rm: new option (-d) to remove empty directories
Add new option to rm (-d/--dir), which allows removal of empty directories, while still safely disallowing removal of non-empty ones. This change improves compatibility with Mac OS X and BSD systems which all have the -d flag. * src/remove.c (rm_fts): allow removal of empty dir if the option is set * src/remove.h (rm_options): new option - remove_empty_directories * src/rm.c (long_opts, usage, main): usage && option parsing * tests/Makefile.am: added new test cases (d-1, d-2) * tests/rm/d-1: new test case - successfully delete empty dir * tests/rm/d-2: new test case - refuse to delete nonempty dir
1 parent a07dfa9 commit 1adbf33

File tree

9 files changed

+105
-4
lines changed

9 files changed

+105
-4
lines changed

NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ GNU coreutils NEWS -*- outline -*-
22

33
* Noteworthy changes in release ?.? (????-??-??) [?]
44

5+
** New features
6+
7+
rm now accepts -d/--dir flag which allows it to remove empty directories.
8+
As removing empty directories is relatively safe this option can be used as
9+
a part of the alias rm='rm --dir'. This change also improves compatibility
10+
with Mac OS X and BSD systems which all have the -d flag.
511

612
* Noteworthy changes in release 8.18 (2012-08-12) [stable]
713

doc/coreutils.texi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8807,6 +8807,13 @@ The program accepts the following options. Also see @ref{Common options}.
88078807

88088808
@table @samp
88098809

8810+
@item -d
8811+
@itemx --dir
8812+
@opindex -d
8813+
@opindex --dir
8814+
@cindex directories, removing
8815+
Remove the listed directories if they are empty.
8816+
88108817
@item -f
88118818
@itemx --force
88128819
@opindex -f

src/mv.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ static void
7373
rm_option_init (struct rm_options *x)
7474
{
7575
x->ignore_missing_files = false;
76+
x->remove_empty_directories = true;
7677
x->recursive = true;
7778
x->one_file_system = false;
7879

src/remove.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,15 @@ rm_fts (FTS *fts, FTSENT *ent, struct rm_options const *x)
414414
switch (ent->fts_info)
415415
{
416416
case FTS_D: /* preorder directory */
417-
if (! x->recursive)
417+
if (! x->recursive
418+
&& !(x->remove_empty_directories
419+
&& is_empty_dir (fts->fts_cwd_fd, ent->fts_accpath)))
418420
{
419-
/* This is the first (pre-order) encounter with a directory.
421+
/* This is the first (pre-order) encounter with a directory
422+
that we can not delete.
420423
Not recursive, so arrange to skip contents. */
421-
error (0, EISDIR, _("cannot remove %s"), quote (ent->fts_path));
424+
int err = x->remove_empty_directories ? ENOTEMPTY : EISDIR;
425+
error (0, err, _("cannot remove %s"), quote (ent->fts_path));
422426
mark_ancestor_dirs (ent);
423427
fts_skip_tree (fts, ent);
424428
return RM_ERROR;

src/remove.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ struct rm_options
4949
/* If true, recursively remove directories. */
5050
bool recursive;
5151

52+
/* If true, remove empty directories. */
53+
bool remove_empty_directories;
54+
5255
/* Pointer to the device and inode numbers of '/', when --recursive
5356
and preserving '/'. Otherwise NULL. */
5457
struct dev_ino *root_dev_ino;

src/rm.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ static struct option const long_opts[] =
7777
{"-presume-input-tty", no_argument, NULL, PRESUME_INPUT_TTY_OPTION},
7878

7979
{"recursive", no_argument, NULL, 'r'},
80+
{"dir", no_argument, NULL, 'd'},
8081
{"verbose", no_argument, NULL, 'v'},
8182
{GETOPT_HELP_OPTION_DECL},
8283
{GETOPT_VERSION_OPTION_DECL},
@@ -154,6 +155,7 @@ Remove (unlink) the FILE(s).\n\
154155
--no-preserve-root do not treat '/' specially\n\
155156
--preserve-root do not remove '/' (default)\n\
156157
-r, -R, --recursive remove directories and their contents recursively\n\
158+
-d, --dir remove empty directories\n\
157159
-v, --verbose explain what is being done\n\
158160
"), stdout);
159161
fputs (HELP_OPTION_DESCRIPTION, stdout);
@@ -189,6 +191,7 @@ rm_option_init (struct rm_options *x)
189191
x->ignore_missing_files = false;
190192
x->interactive = RMI_SOMETIMES;
191193
x->one_file_system = false;
194+
x->remove_empty_directories = false;
192195
x->recursive = false;
193196
x->root_dev_ino = NULL;
194197
x->stdin_tty = isatty (STDIN_FILENO);
@@ -220,10 +223,14 @@ main (int argc, char **argv)
220223
/* Try to disable the ability to unlink a directory. */
221224
priv_set_remove_linkdir ();
222225

223-
while ((c = getopt_long (argc, argv, "firvIR", long_opts, NULL)) != -1)
226+
while ((c = getopt_long (argc, argv, "dfirvIR", long_opts, NULL)) != -1)
224227
{
225228
switch (c)
226229
{
230+
case 'd':
231+
x.remove_empty_directories = true;
232+
break;
233+
227234
case 'f':
228235
x.interactive = RMI_NEVER;
229236
x.ignore_missing_files = true;

tests/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ TESTS = \
9898
chgrp/basic \
9999
rm/dangling-symlink \
100100
misc/ls-time \
101+
rm/d-1 \
102+
rm/d-2 \
101103
rm/deep-1 \
102104
rm/deep-2 \
103105
rm/dir-no-w \

tests/rm/d-1

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/sh
2+
# Test "rm --dir --verbose".
3+
4+
# Copyright (C) 2012 Free Software Foundation, Inc.
5+
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
19+
. "${srcdir=.}/init.sh"; path_prepend_ ../src
20+
print_ver_ rm
21+
22+
mkdir a || framework_failure_
23+
> b || framework_failure_
24+
25+
rm --verbose --dir a b > out || fail=1
26+
27+
cat <<\EOF > exp || framework_failure_
28+
removed directory: 'a'
29+
removed 'b'
30+
EOF
31+
32+
test -e a && fail=1
33+
test -e b && fail=1
34+
35+
# Compare expected and actual output.
36+
compare exp out || fail=1
37+
38+
Exit $fail

tests/rm/d-2

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/sh
2+
# Ensure that 'rm -d dir' (i.e., without --recursive) gives a reasonable
3+
# diagnostic when failing.
4+
5+
# Copyright (C) 2012 Free Software Foundation, Inc.
6+
7+
# This program is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published by
9+
# the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
12+
# This program is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
20+
. "${srcdir=.}/init.sh"; path_prepend_ ../src
21+
print_ver_ rm
22+
23+
mkdir d || framework_failure_
24+
> d/a || framework_failure_
25+
26+
rm -d d 2> out && fail=1
27+
printf "%s\n" \
28+
"rm: cannot remove 'd': Directory not empty" \
29+
> exp || framework_failure_
30+
31+
compare exp out || fail=1
32+
33+
Exit $fail

0 commit comments

Comments
 (0)