If you want to find the first subfolder regarding alphanumerical ordering (that, beware, depends on your local settings), you could use this little Bash snippet:
shopt -s nullglob
shopt -u dotglob
a=( */ )
echo "${a[0]}"
this will just echo the first subfolder regarding Bash's alphanumerical sorting.
If you want to remove it, adding a few guards:
shopt -s nullglob
shopt -u dotglob
a=( */ )
[[ -d "${a[0]}" ]] && echo rm -rfv -- "${a[0]}"
Now, I'm not sure I would personally use this on a production server. It depends how sure I am the content of the directory is (and how sure I am of by backups).
The shopt -s nullglob
and shopt -u dotglob
are here to avoid some silly mistake that could occur if they were set otherwise. Also, please observe the quotes and do not alter them whatsoever.
Oh, I left an echo
in front of the rm
. Do remove it only when you're sure this works exactly as you expect, after several testings. As a one-liner:
shopt -s nullglob; shopt -u dotglob; a=( */ ); [[ -d "${a[0]}" ]] && echo rm -rfv -- "${a[0]}"
If you want to specify the path the subfolders are in, just replace the a=( */ )
statement with:
a=( /path/to/folder/*/ )
make sure you put an absolute path, and that you end with */
(with a trailing slash). If you have spaces in your path, use quotes as so:
a=( '/path with/spaces to/folder/'*/ )
(observe how the */
is outside the quoted part, and there are no spaces after the closing quote and the */
).
Now if you really want to determine the oldest subfolder (with a certain indetermination if several have the same age) you could (shudder) parse the output of ls
sorted by time in reverse order and take the first and remove it, as in:
ls -1tr --group-directories-first /path/to/folder | head -1 | xargs echo rm -rfv
but this will fail if there are no directories (you would erase the oldest file instead), or fail if there are files containing spaces or funny symbols. Don't do this.
You could instead ask for ls */
without listing the content of the directories with the -d
option:
ls -1dtr path/to/folder/*/ | head -1 | xargs echo rm -rfv
but this would fail miserably if there are spaces in the name. Don't do this.
You could then try to fix this by use the -Q
option to ls
(i.e., quote the output):
ls -1dtQr path/to/folder/*/ | head -1 | xargs echo rm -rfv
but this fails if there are quotes or newlines in a filename. Don't do this.
Then you'd try xargs
with the -0
option:
ls -1dtQr path/to/folder/*/ | head -1 | xargs -0 echo rm -rfv
but this fails if there are newlines in a filename. Don't do this.
Then you could... STOP!
If you really want a 100% bullet proof method, that might not be the most efficient, though, but that will suit you if security is your top one priority, here's a possibility (in Bash):
shopt -s nullglob
shopt -u dotglob
where="/path with spaces is ok/to/folder"
for i in "$where"/*/; do
if ! find "$where" -mindepth 1 -maxdepth 1 \! -name '.*' -type d \! -newer "$i" \! -samefile "$i" -print | read; then
echo rm -rfv -- "$i"
break
fi
done
This is not the most efficient, but works very well: it loops through all the (first-level) directories i
in given directory where
and launches find
to get the (non-hidden) directories older than the directory i
and if it finds any it does nothing; if it finds none, then we break echo rm
it and break the loop. The side effect is that the directory that will be deleted is the oldest and first with respect to Bash's alphanumerical ordering.
You could even make a cool script out of it (call it e.g., rm_oldest_dir
):
#!/bin/bash
if [[ $1 = --serious ]]; then
serious=()
shift
else
serious=("echo")
fi
if [[ -z $1 ]]; then
echo >&2 "You must provide an argument"
exit 1
fi
where=$1
if [[ ! -d $where ]]; then
echo >&2 "\`$where' is not a directory"
exit 1
fi
shopt -s nullglob
shopt -u dotglob
for i in "$where"/*/; do
if ! find "$where" -mindepth 1 -maxdepth 1 \! -name '.*' -type d \! -newer "$i" \! -samefile "$i" -print | read; then
"${serious[@]}" rm -rfv -- "$i"
if ((${#serious[@]})); then
echo "*** Nothing has been removed. Call with option --serious to really remove. ***"
fi
break
fi
done
Then use as, e.g.,
$ mkdir Test
$ cd Test
$ mkdir oldest; sleep 1; mkdir lots_of_dirs{1..10}
$ rm_oldest_dir .
rm -rfv -- ./oldest/
*** Nothing has been removed. Call with option --serious to really remove. ***
$ ls
lots_of_dirs1 lots_of_dirs3 lots_of_dirs6 lots_of_dirs9
lots_of_dirs10 lots_of_dirs4 lots_of_dirs7 oldest
lots_of_dirs2 lots_of_dirs5 lots_of_dirs8
$ rm_oldest_dir --serious .
removed directory: `./oldest'
$ ls
lots_of_dirs1 lots_of_dirs2 lots_of_dirs4 lots_of_dirs6 lots_of_dirs8
lots_of_dirs10 lots_of_dirs3 lots_of_dirs5 lots_of_dirs7 lots_of_dirs9
$
Neat.
You could then put it in your crontab as
0 0 * * * rm_oldest_dir --serious "/path with spaces/to/where/you/want"
The only attack against this script I can think of is if there are too many folders: Bash's globbing is not very efficient, and too many folders might lead to a very high CPU usage for a long time and eventually end as an abortion. A more involved method would be a file-based half quicksort but as this post is already too long, I will end it here.
Cheers!
I don't know if the second part of this answer really has its place on SF. If not, please let me know in the comments or downvote this post!