3
\$\begingroup\$

Inspired by the a recent question to rename files by editing the list of names in an editor, I put together a similar script.

Why: if you need to perform some complex renames that are not easy to formulate with patterns (as with the rename.pl utility), it might be handy to be able to edit the list of names in a text editor, where you will see the exact names you will get.

Features:

  • Edit names in a text editor
  • Use the names specified as command line arguments, or else the files and directories in the current directory (resolve *)
  • Use a sensible default text editor
    • According to man bash, READLINE commands try $VISUAL or else $EDITOR -> looks like a good example to follow.
    • Abort if cannot determine a suitable editor.
  • Abort (do not rename anything) if the editor exits with error
  • Paths containing newlines are explicitly unsupported
  • Perform basic sanity checks: the edited text should have the same number of lines as the paths to rename

Here's the script:

#!/usr/bin/env bash
#
# SCRIPT: mv-many.sh
# AUTHOR: Janos Gyerik <info@janosgyerik.com>
# DATE:   2019-07-27
#
# PLATFORM: Not platform dependent
#
# PURPOSE: Rename files and directories by editing their names in $VISUAL or $EDITOR
#

set -euo pipefail

usage() {
    local exitcode=0
    if [[ $# != 0 ]]; then
        echo "$*" >&2
        exitcode=1
    fi

    cat << EOF
Usage: $0 [OPTION]... [FILES]

Rename files and directories by editing their names in $editor

Specify the paths to rename, or else * will be used by default.
Limitations: the paths must not contain newline characters.
EOF

    if [[ $editor == *vim ]]; then
        echo "Tip: to abort editing in $editor, exit with :cq command."
    fi

    cat << "EOF"

Options:
  -h, --help         Print this help

EOF
    exit "$exitcode"
}

fatal() {
    echo "Error: $*" >&2
    exit 1
}

find_editor() {
    # Following READLINE conventions, try VISUAL first and then EDITOR

    if [[ ${VISUAL+x} ]]; then
        echo "$VISUAL"
        return
    fi

    # shellcheck disable=SC2153
    if [[ ${EDITOR+x} ]]; then
        echo "$EDITOR"
        return
    fi

    fatal 'could not determine editor to use, please set VISUAL or EDITOR; aborting.'
}

editor=$(find_editor)

oldnames=()
while [[ $# != 0 ]]; do
    case $1 in
    -h|--help) usage ;;
    -|-?*) usage "Unknown option: $1" ;;
    *) oldnames+=("$1") ;;
    esac
    shift
done

work=$(mktemp)
trap 'rm -f "$work"' EXIT

if [[ ${#oldnames[@]} == 0 ]]; then
    oldnames=(*)
fi

printf '%s\n' "${oldnames[@]}" > "$work"
"$editor" "$work" || fatal "vim exited with error; aborting without renaming."
mapfile -t newnames < "$work"

[[ "${#oldnames[@]}" == "${#newnames[@]}" ]] ||
    fatal "expected ${#oldnames[@]} lines in the file, got ${#newnames[@]}; aborting without renaming."

for ((i = 0; i < ${#oldnames[@]}; i++)); do
    old=${oldnames[i]}
    new=${newnames[i]}
    if [[ "$old" != "$new" ]]; then
        mv -vi "$old" "$new"
    fi
done

What do you think? I'm looking for any and all kinds of comments, suggestions, critique.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.