As part of repository maintenance, I often use a script to delete a group of directories, even if those directories are not empty. It looks something like this:
(require '[clojure.java.io :as io])
.
.
.
(defn delete-children-recursively! [f]
"Given a file, delete it. If the file is a directory delete its
contents first. This version does not handle symlinks."
(when (.isDirectory f)
(doseq [f2 (.listFiles f)]
(delete-children-recursively! f2)))
(when (.exists f) (io/delete-file f)))
(defn delete-clean-targets! [v]
"Given a vector of directory names (strings), delete the directories
and any files they contain."
(doseq [dir-name v]
(let [dir-file (io/file dir-name)]
(delete-children-recursively! dir-file))))
(delete-clean-targets! clean-targets)
(System/exit 0)
Somewhere, clean-targets
is defined as a vector of directory names. When passed to the delete-clean-targets!
function, all of those directories and their contents are removed.
But if any of the directories contain a symlink, it will not be deleted, and the directory where it resides will not be deleted either since it will not be empty.
The Files
class provides a way to handle things such that the file can be deleted even if it is a symlink. The revised function looks something like this:
(import [java.nio.file Files LinkOption Path Paths])
.
.
.
(defn delete-children-recursively! [f]
"Given a file, delete it. If the file is a directory delete its
contents first. This version handles symlinks by deleting the link,
not the file it links to."
(when (.isDirectory f)
(doseq [f2 (.listFiles f)]
(delete-children-recursively! f2)))
(let [file-name (.getAbsolutePath f)
fp (Paths/get file-name (into-array String []))]
(try
(when (Files/exists fp (into-array [java.nio.file.LinkOption/NOFOLLOW_LINKS]))
(Files/delete fp))
(catch Exception e
(println "Problem trying to delete " fp)
(println "e: " e)))))
There are a couple of interesting changes here, particularly the handling of optional arguments. For interoperability, those arguments get packed up into a native Java array as shown in the call to Files/exists
with the inclusion of the NOFOLLOW_LINKS
argument. It is also required when there are no additional argument, such as the call to Paths/get
.
Note that using the NOFOLLOW_LINKS
option means that only the symlink will be deleted, not the file that it points at.