4
\$\begingroup\$

Inspired by 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 <[email protected]>
# 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.

Toby Speight
87.7k14 gold badges104 silver badges325 bronze badges
asked Jul 27, 2019 at 14:12
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Looks pretty good to me, and to Shellcheck too.

find_editor is quite long-winded; it could be a one-liner:

editor=${VISUAL:-${EDITOR:?could not determine editor to use, please set VISUAL or EDITOR; aborting.}}

There's a couple of Vim-isms remaining. The test if [[ $editor == *vim ]] could be generalised to a case "$editor" in ... esac to support adding more editor hints, but more significantly, we have an error message that mentions vim where $editor would be more appropriate.

The current implementation is very simplistic when the new names might overlap with the old names. We might need a topological sort to perform the renames in the correct order in that case.

Perhaps it should be an error if the user asks for two or more files to be moved to the same target name?

answered Feb 28, 2022 at 11:25
\$\endgroup\$
1
  • \$\begingroup\$ Thanks! Great point about multiple renames to the same file, that could lead to data loss. \$\endgroup\$ Commented Mar 2, 2022 at 15:44

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.