Extended Sidebar: Details of the File-Renaming Loops

Earlier in the article Filenames by Design, Part Two I showed two shell loops that rename an ordered list of files (read from /tmp/files). Each loop builds a series of shell commands to rename the files with a prefix of either three decimal digits or a variable number of hexadecimal digits. The prefix insures file sorting order for shell wildcards and ls(1). Each loop writes a shell script to /tmp/renamer that renames the files.

This “sidebar” page has more information about those two loops.

First loop: numeric prefixes


$ let i=0
$ while read -r oldfile
> do
>   printf -v prefix '%03d' $((i++))
>   echo "mv -i '$oldfile' '${prefix}_$oldfile'"
> done </tmp/files >/tmp/renamer

Some of the techniques above might not be familiar. Here’s a rundown:

Here’s a look at the two files we made, /tmp/files and /tmp/renamer:


$ head -3 /tmp/files
foo
bar
data
$ less /tmp/renamer
mv -i 'foo' '000_foo'
mv -i 'bar' '001_bar'
mv -i 'data' '002_data'
.
mv -i 'a file' '124_a file'
mv -i 'How
many
people?' '125_How
many
people?'

Note that the single-quoting even protects filenames containing spaces, newlines, and wildcard characters like ?. (Yes, a multi-line filename is legal on Linux filesystems.) This keeps the shell from breaking the last two filenames into pieces and from interpreting the question mark (?) as a wildcard.

(You might want to modify the script that builds /tmp/renamer, or simply edit /tmp/renamer by hand, to make a more “agreeable”
new filename without spaces or special characters — for instance, 125_How_many_people.)

Q: Our method of single-quoting the mv arguments fails for one particular character. Which one?

A: If a filename contains a single quote character ('), its mv command will have unmatched single quotes.

Writing shell commands to generate other shell commands that can deal with filenames containing any arbitrary mixture of single and double quotes (especially with backslashes before them) can be a challenge. A simple workaround is adding a test to warn you that you’ll need to edit /tmp/renamer by hand:


while read -r oldfile
do
  case "$oldfile" in
  *\'*) echo "WARNING: fix the unprotected ' in \"$oldfile\"." 1>&2 ;;
  esac
.


Or you could modify the script to handle filenames containing single quotes and double quotes. (We don’t shy away from the good, the bad, or the ugly here in the Power Tools column. :) It’s probably easier to use a different programming language than the shell — possibly for the whole job.
But here’s a workaround. It uses sed, which doesn’t treat quotes specially. (If you have a simpler way using only shell code, tell me! Please be sure it handles filenames containing the two-character sequence \".)


while read -r oldfile
do
  printf -v prefix '%03d' $((i++))
  case "$oldfile" in
  *[\'\"]*)
    # Make mv -i "$oldfile" "${prefix}_$oldfile"
    echo -E "$oldfile" | sed \
    -e 's/\\/&&/g' \
    -e 's/"/\\"/g' \
    -e 's/.*/"&"/' \
    -e h \
    -e "s/^\"/\"${prefix}_/" \
    -e x -e G -e 's/\n/ /' \
    -e 's/^/mv -i /'
    ;;
  *)
    # Output single-quoted arguments:
    echo "mv -i '$oldfile' '${prefix}_$oldfile'"
    ;;
  esac
done </tmp/files >/tmp/renamer


A detailed explanation would take pages. Briefly, though:

Second loop

file_count=$(wc -l < /tmp/files)
max_hex=$(echo -e "obase=16\n${file_count}-1" | bc -q)
prefix_width=${#max_hex}
for prefix in $(jot -w "%0${prefix_width}x" "$file_count" 0)
do
  read -r oldfile
  echo "mv -i '$oldfile' '${prefix}_$oldfile'"
done </tmp/files >/tmp/renamer

Here’s the rundown:

Jerry Peek is a freelance writer and instructor who has used Unix and Linux for more than 25 years. He's happy to hear from readers; see https://www.jpeek.com/contact.html.

[Read previous article] [Read next article]
[Read Jerry’s other Linux Magazine articles]