my favorite shell scripts: mvdir


To introduce this collection of technical notes, I thought I’d start with one of my favorite shell scripts. I wrote this over ten years ago, and I still use it very often. The idea is similar to rename, but interactive, so much easier to use.

It opens up a text editor on the filenames in the current directory (or directories given on the command line). You can edit the filenames using any editor functions you like. Obviously, regex search-and-replace is indispensable at this point, and I also find vim’s visual block mode quite useful.

Upon saving and exiting the editor, files are renamed according to your edits. The script correctly handles swapping filenames (though not conflicts). Error exists from the editor abort the operation (:cq in vim). Renaming a file to the empty string deletes it, so the script is also useful for interactively cleaning up a directory.

The script should work in any Bourne-compatible shell, not just bash.

#!/bin/sh
# mvdir
# David Reiss <dnr@dnr.im>
# Created: 2002-07-21
# Last update: 2012-12-06

t1=`mktemp`
t2=`mktemp`
t3=`mktemp`

[ $# -gt 0 ] || set .

for d in "$@"; do
(
  cd "$d" || continue

  ls > $t1
  cp -f $t1 $t2

  ${EDITOR:-vi} $t2
  status=$?
  [ $status -ne 0 ] && exit $status

  if [ `< $t1 wc -l` -ne `< $t2 wc -l` ]; then
    echo "line count mismatch"
    rm -f $t1 $t2 $t3
    exit 1
  fi

  < $t2 sed -e "s/^-/.\/-/" -e "s/'/'\\\\''/g" -e "s/.*/'&'/" > $t3
  < $t1 sed -e "s/^-/.\/-/" -e "s/'/'\\\\''/g" -e "s/.*/'&'/" > $t2
  paste -d, $t2 $t3 | sed '/^\(.*\),\1$/d' | nl -s, > $t1
  (
    < $t1 sed -ne "s/^ *\(.*\),'\(.*\)','\(\)'$/rm '\2'/p" \
              -ne "s/^ *\(.*\),'\(.*\)','\(.*\)'$/mv '\2' '##mvdir\1'/p"
    < $t1 sed -ne "s/^ *\(.*\),'\(.*\)','\(..*\)'$/mv '##mvdir\1' '\3'/p"
  ) | sh -v

  rm -f $t1 $t2 $t3
)
status=$?
[ $status -ne 0 ] && echo "$0: aborted" && exit $?
done