Linux mv and file patterns

Last week I ran into an interesting issue renaming a bunch of files in my machine. The command I ran was:

$ mv *.jpeg *.jpg

I was expecting this command to rename all .jpeg files to .jpg but instead the command returned

$ mv *.jpeg *.jpg
usage: mv [-f | -i | -n] [-v] source target
       mv [-f | -i | -n] [-v] source ... directory

At first I thought I had a typo in my command but the reality is that this is not a valid mv command.

The culprit is that my shell (Bash) is expanding *.jpeg and *.jpg before executing the mv command and understandably mv refuses to continue. To illustrate this let's assume there are two .jpeg files and no .jpg files on my machine, when I execute my original command what Bash passes to mv is something like this:

$ mv file1.jpeg file2.jpeg *.jpg

Notice that Bash expanded *.jpeg into the two individual files and *.jpg stayed as-is since there is nothing to expand it into. Certainly not what I was intending to do. A few searches on the web showed many people having this same problem, here is one example.

Working around this issue was easy enough. I wrote a quick shell script to do the job:

for FILE in `ls -1 *.jpeg`
do
    BASENAME=`basename $FILE .jpeg`
    TARGET=$BASENAME".jpg"
    mv $FILE $TARGET
done

I posted about this issue on Twitter and got several suggestions on how to make this script better. The solution provided by John Borwick in particular is very neat:

for FILE in *.jpeg
do
    TARGET="${FILE%.jpeg}.jpg"
    mv "$FILE" "$TARGET"
done

A few other people suggested that I use either rename or mmv to do what I was trying to do. Both of these utilities can be installed easily on Mac or Linux systems.

Using mmv the syntax to rename the files is:

$ mmv -n "*.jpeg" "#1.jpg"

Using rename the syntax is:

$ rename -xa .jpg *.jpeg

One of the things that dawned on me while reading the blog post for mmv and the local man pages for rename (1) was that my original command would have worked in DOS, and I was a DOS/Windows user a long time ago. The mmv blog post even says

Under good old DOS you could rename multiple files by saying
REN A*.* B*.*

but the reason this worked is because DOS had pretty simple shell that didn't support Unix style pathname pattern expansion (glob). The man page for rename (1) on my system even has the following note:

...
-g, --glob
Glob filename arguments. This is useful if you're using a braindead shell such as cmd.exe which won't expand wildcards on behalf of the user.
...

So that explains why I was so puzzled. I probably did rename files with a command "like this" before, but it must have been 10 years or so ago and in a different operating system :)

Note: Beware that rename is a native system function in Linux systems and man rename might give you information about the function rename (2) rather than the program rename (1) if the program is not installed on your machine. See also.

Blog posted on: 2019-10-29 23:22:08 +0000 UTC