Here is a reformatted version of the script:
( # start a subshell to isolate env changes
IFS=/ # split on /
set -- /Dropbox/apache2-backup* # set arg array to last glob, and:
set -ef # EXPANDED # turn off globbing (-f) and die on errors (-e)
for f in $(\ls -rtd "$@"; echo /); do # iterate over sorted/split array:
# skip if it deleting the longest match of D* gives nothing
# i.e. starts with D or is empty string, => is the dirname or some ^/ leftover
[ -n "${f##D*}" ] || continue # case "$f" in ''|D*) continue;; esac
# do the mv, ls sorts things
mv "${1%/*}/${f%?}" "/root${1%/*}-archive" # add `&& break` to do only one mv
done
)
You can also see a machine-annotated version on ExplainShell. That one looks cooler.
The set
line is the last place where globs take place. Like what mikeserv said, people would
prefer to say this in bash like foo=(/Dropbox/apache2-backup*)
, do the set -ef
separately
and use "${foo[@]}"
with that ls
.
The point is now the parameter, well, array, holds either a list of such files or a failed glob.
To print a list out, people often use the bash printf '%q '
trick to see what the word list
should look like if passed as arguments on a command line. Since we are too lazy to fake some files
with date orders like that, this is only a placeholder.
The magic is about the word list expanded from $(\ls)
stuff, so let's break this up.
printf '%q ' $(\ls -rtd "$@"; echo /)
Here ls
is asked to:
-d
: Don't expand directories. This prevents things from messing up.-t
: Sort by modification time.-r
: Reverse order when sorting, so we get the oldest first.- Run these on the list generated by the glob and stored by
set
.
Note that the previous -e
setting doesn't work in ls
now, so it can't handle
a failed glob. \ls
is used to avoid alias expansion.
Let's strip off those sorting things (creating or touching files is simply annoying) and see:
IFS=/
p="$(mktemp -d /tmp/tmp.XXX)"
touch "$p/a" "$p/b" "$p/c"; mkdir "$p/I_am_a_dir"
echo ls:
\ls -d "$p"/*; echo /
echo
echo The list after splitting by IFS:
printf '%q ' $(\ls -d "$p"/*; echo /)
echo After some filtering:
for i in $(\ls -d "$p"/*; echo /); do case "$i" in ''|tmp*) continue;; esac; echo I got "$i"; done
unset IFS
We can see a problem in our sample: all $i
gives an extra newline. To work around
this, mikeserv uses "${f%?}" to strip off the last character, the newline.
${1%/*}
is like $(basename "$1")/...
, only faster.
dang. thanks. the
echo /
is important, too, by the way. it ensures the$(command sub)
doesnt strip trailing newlines (except the oneecho
ed). it means the${f%?}
works even for the last param.