#!/bin/sh # bulkrename: bulk rename files using $EDITOR and temporary files # this file in public domain EDITOR="${EDITOR:-"${VISUAL:-"vi"}"}" err() { printf "%s: %s\n" "${0##*/}" "$@" >&2 exit 1 } hascollision() { i=0 for arg do if test "$i" -lt "$nargs" then test "$target" = "$arg" && return else break fi i=$((i+1)) done false } # create temporary files if ! tmpfile="$(mktemp)" then err "could not make temporary file" fi trap 'rm -f "$tmpfile"' EXIT # if no argument is passed, fill $@ with lines from stdin if [ $# -eq 0 ] then # run on stdin while read -r line do set -- "$@" "$line" done fi # check if arguments are valid and write them to tmpfile nargs="$#" for arg do case "$arg" in ("") err "empty argument" ;; (*[[:cntrl:]]*) err "control characters in filenames are not supported" ;; ([[:space:]]*|*[[:space:]]) err "filenames beginning or ending in space is not supported" ;; esac if ! test -e "$arg" then err "$arg: file does not exist" fi printf "%s\n" "$arg" >> "$tmpfile" done # call editor on tmpfile "$EDITOR" -- "$tmpfile" </dev/tty # read new filenames from edited tmpfile while read -r target && test "$nargs" -gt 0 do source="$1" # for each target filename read from the file there is a # corresponding entry in $@. We read filenames from the # file up to $nargs, and append $@ with commands: # "rm" "file" (remove file) # "mv0" "from" "to" (move file on the next loop) # "mv1" "from" "to" (move file on the last loop) if test -z "$target" then # line asks to remove file set -- "$@" "rm" "$source" elif test "$target" = "$source" then # line does nothing : elif hascollision "$@" then # line renaming creates filename collision; get unique filename unique="$source" while test -e "$unique" do unique="$unique~" done set -- "$@" "mv0" "$source" "$unique" set -- "$@" "mv1" "$unique" "$target" else # line is a renaming without collision set -- "$@" "mv0" "$source" "$target" fi # we're done with this source file shift nargs="$((nargs - 1))" done <"$tmpfile" # remove remaining arguments (in case file has more lines than we have files) shift "$nargs" # perform the commands specified in the argument list while test "$#" -gt 0 do case "$1" in ("rm") rm -- "$2" printf "rm %s\n" "$2" shift 2 ;; ("mv0") mv -- "$2" "$3" printf "%s -> %s\n" "$2" "$3" shift 3 ;; ("mv1") set -- "$@" "mv2" "$2" "$3" shift 3 ;; ("mv2") break ;; (*) err "$1: unknown command" ;; esac done # perform the commands specified in the argument list (the final ones) while test "$#" -gt 0 do case "$1" in ("mv2") mv -- "$2" "$3" printf "%s -> %s\n" "$2" "$3" shift 3 ;; (*) err "$1: unknown command" ;; esac done