Skip to content

Instantly share code, notes, and snippets.

@Artoria2e5
Last active December 8, 2015 18:14
Show Gist options
  • Save Artoria2e5/8b602f252f7d966705ed to your computer and use it in GitHub Desktop.
Save Artoria2e5/8b602f252f7d966705ed to your computer and use it in GitHub Desktop.
Mikeserv's script

Explanation

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.

After that set

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.

On the for f line

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

Parameter Expansion on mv line

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.

@mikeserv
Copy link

mikeserv commented Dec 8, 2015

dang. thanks. the echo / is important, too, by the way. it ensures the $(command sub) doesnt strip trailing newlines (except the one echoed). it means the ${f%?} works even for the last param.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment