Having the Busybox ash shell at hand, I wish to cleanup the /opt folder.

Cleanup means to remove every file and folder recursively except for a few exclusion paths that need to be left untouched.

Regression rm

# cd /opt && echo rm -rf \
-sh: syntax error: unexpected "("

Regression find

This seems to do the job:

 # cd /opt && find ./ ! -path "./etc" ! -path "./etc/config" ! \
     -path "./etc/config/*" ! -path "./etc/nginx" ! -path "./etc/nginx/*" ! \
     -path "./home" ! -path "./home/*" ! -path "./usr" ! -path "./usr/sbin" ! \
     -path "./usr/sbin/*" ! -path "./var" ! -path "./var/mlocate" ! \
     -path "./var/mlocate/*" -exec rm -f {} \;; cd -

Any suggestion how to improve? Use -delete instead of -exec rm -f {} \? Handle rm: can't remove '.' or '..'? How to delete the empty directories without recursion rm -rf, in other words how to f.e. integrate rmdir?

Note: it's Busybox 1.24.2 and there is no shopt neither tac available. Defined functions:

[, [[, addgroup, adduser, ar, arping, ash, awk, base64, basename, blkid, blockdev, brctl, bunzip2, bzcat,
cat, chgrp, chmod, chown, chroot, clear, cmp, cp, crond, crontab, cryptpw, cut, date, dd, delgroup, deluser,
devmem, df, diff, dirname, dmesg, dos2unix, du, echo, egrep, env, expr, false, fgrep, find, free, fsync,
getty, grep, gunzip, gzip, halt, head, hexdump, hostid, hostname, id, ifconfig, insmod, iostat, ip, ipaddr,
ipcalc, iplink, ipneigh, iproute, iprule, iptunnel, kill, killall, klogd, less, ln, lock, logger, login,
losetup, ls, lsmod, lsusb, md5sum, mkdir, mkfifo, mknod, mkswap, mktemp, more, mount, mv, nc, netmsg,
netstat, nice, nslookup, ntpd, od, passwd, patch, pgrep, pidof, ping, ping6, pivot_root, poweroff, printf,
ps, pwd, readlink, reboot, renice, reset, rev, rm, rmdir, rmmod, route, run-parts, sed, seq, setconsole,
setserial, sh, sha1sum, sha256sum, sha512sum, sleep, sort, start-stop-daemon, stat, strings, stty, su,
swapoff, swapon, switch_root, sync, sysctl, syslogd, tail, tar, tee, telnet, telnetd, test, time, top, touch,
tr, traceroute, true, tty, udhcpc, umount, uname, uniq, unix2dos, unlink, unlzma, unxz, unzip, uptime,
usleep, vconfig, vi, wc, wget, which, xargs, xz, xzcat, yes, zcat
  • What needs improving? – Jeff Schaller Sep 6 '18 at 23:37
  • @JeffSchaller To -delete or `rm -f {}`? Write all parent paths or shorten it to the longest path and ignore that non-empty directories can't be deleted? – Pro Backup Sep 6 '18 at 23:42


cd /opt &&
  find . \( \
    -path ./etc/config -o \
    -path ./etc/nginx -o \
    -path ./home -o \
    -path ./usr/sbin -o \
    -path ./var/mlocate \) -prune -o \( \
      -type d -print0 -o -exec rm -f {} + \) |
    tr '\0\n' '\n\0' |
    tac |
    tr '\0\n' '\n\0' |
    xargs -r0 rmdir

-prune makes sure we don't even enter those skipped directories.

We delete non-directory files in the other directories. And leave the directories to process for after (tac won't output anything until find finishes because it starts with the last line).

We need to pass the list of directories in reverse order to rmdir as we need it do delete the content before the directory itself. Note that rmdir won't delete non-empty directories.

One usually uses -depth to process leaves before the branches that hold them, but we can't use -depth with -prune. For the same reason, we can't use -delete as it implies -depth.

  • It looks quite genius, first process the files, and when it's a directory print them with null separators for later processing. My bad luck as that this Busybox has support for cat but not for the reverse output command tac build-in. – Pro Backup Sep 7 '18 at 16:25
  • Not having tac I'd prefer the longer and less readable command without prune and with -depth and -delete: cd /opt && find . ! -path "." ! -path "./etc" ! -path "./etc/config" ! \ -path "./etc/config/*" ! -path "./etc/nginx" ! -path "./etc/nginx/*" ! \ -path "./home" ! -path "./home/*" ! -path "./usr" ! -path "./usr/sbin" ! \ -path "./usr/sbin/*" ! -path "./var" ! -path "./var/mlocate" ! \ -path "./var/mlocate/*" \( -type d -print0 -o -delete \) | xargs -r0 rmdir; cd - >/dev/null – Pro Backup Sep 7 '18 at 18:20


According to jlliagre's answer at find - exec rm vs -delete \; can better be replaced with + to optimize the exec clause by not running the rm command for each and every match present on the file system.


It needs an additional ! -path "./" to not generate rm: can't remove '.' or '..'.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.