1

How can I grab the first subfolder in a directory and delete it?

I found a script to monitor the free diskspace.

It sends an alert email if space runs low, but I want to also delete some unneeded stuff.

I have a backup folder where I save daily and monthly backups. I want to delete the first folder since this always the oldest, but I don't know the name of the oldest backup.

My folders without Jan-May and Dec:

06-01
07-01
08-01
09-01
10-01
11-01
Friday
Monday
Saturday
Sunday
Thursday
Tuesday
Wednesday

How can I delete the first folder "06-01" without knowing its name?

Martin
  • 220
  • 3
  • 9
  • What do you mean by _first subfolder_ in directory? Do you mean `.`? (I guess not `:D`). – gniourf_gniourf Nov 02 '13 at 14:57
  • I edited my top post. I hope it's clear now. – Martin Nov 02 '13 at 15:02
  • 2
    With respect, it sounds like what you *really* want to do is to delete the oldest directory. Whilst your naming convention suggests that will be first in the listing, if you say what you want to accomplish, rather than how you think you'll accomplish it, it opens up more possibilities for doing it right. – MadHatter Nov 02 '13 at 15:47

1 Answers1

3

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!

  • I thought about saving `ls my/backups -1 | head -n 1` to a variable and them `rmdir` that. – Martin Nov 02 '13 at 15:11
  • @Martin parsing the output of `ls` is considering a terrible and dangerous practice. Do not use it! – gniourf_gniourf Nov 02 '13 at 15:12
  • Ok. Can I add a path? Say the backupfolder is my/test and the first subfolder is my/test/06-01. The script is run from a different location. – Martin Nov 02 '13 at 15:38
  • @Martin no problem, please see my edited post. – gniourf_gniourf Nov 02 '13 at 15:45
  • Thank you! not much more effort actually. If say that's safer then I'll go for that solution. About the backups: I download them to local storage from time to time so the months old stuff is really useless and sits there "just in case". – Martin Nov 02 '13 at 16:15