How to use zmv — Z Shell’s super-smart file renamer

I’d like to introduce you — if you’re not already acquainted — to the Z Shell’s incredibly handy function zmv. If you ever need to change at the command line the names of a batch of files consistently, it’s the tool you’ll want to turn to first. It’s not well known, and having been given the nod by a colleague, I thought I’d explore and pass on some notes about taking advantage of it.

As I say, zmv is a function provided by the Z Shell, but it’s not made available by default. To ensure that it is, you need to add autoload zmv to your .zshrc file. Likely you have some other autoloads in there already. Restart your Terminal to activate it. Now you can call zmv like any other command.

The basic structure of a zmv call is zmv 'input_pattern' 'output_pattern'. The patterns are included in single quotes so that they’re not interpreted first by the shell and then passed to the command, though there is another way to avoid this. Here’s an example. Recently I had a batch of output files that I’d mis-spelled:

Fightners 01 The Test.mp4
Fightners 02 Goodbye My Lovely.mp4
Fightners 03 What Larks.mp4
Fightners 04 The Classroom.mp4

zmv provides an easy way to change the wrong part of each filename while retaining the correct part — and do so across all files that match the input pattern. So I used:

zmv 'Fightners (*)' 'The Frighteners $1'

As you might expect, the first pattern finds all files that begin with Fightners followed by a space; the rest of the pattern matches against anything. The brackets are standard Z Shell syntax: match the enclosed pattern. zmv passes the result of the match into the output pattern as the variable $1. If you had multiple pairs of brackets, they’d be accessed in the output as $1, $2, $3 etc., in order.

Using the first file above as an example, it’s found by the input pattern and $1 becomes 01 The Test.mp4. This is then passed into the output pattern to make The Frighteners 01 The Test.mp4. Then it repeats for all the other found files.

Here’s an example of multiple output variables, changing the 6 in the middle of the filename to a 1:

zmv '(*)6(???.HEIC)' '${1}1${2}'

I’ve used the ${var} form for referencing variables here to avoid confusion between $11 (variable 1 followed by 1) and $11 (variable 11).

Before committing yourself, you should run the command with the -n switch. This performs a dry run so you can see exactly what the results are going to be. This gets particularly important once you start using complex patterns.

Counting Files

zmv can also make use of shell variables, which allows you to do neat things like renumbering a bunch of files:

count=1 ; zmv '(*)_*.jpg' '$1-$((count++)).jpg'

This sets the variable count, which is then accessed (and incremented) in the output pattern. As usual the pair double-brackets in the output treat the variable as integer, which is then incremented.

So if I have a stack of IMG_4xxx.jpg files, they’ll be renumbered neatly to IMG-x.jpg starting at whatever count is initialized to, 1 in this case.

Don’t like that uppercase IMG? Then use Z Shell’s standard substitution ${VAR_NAME:l} to make them all lower case:

count=1 ; zmv '(*)_*.jpg' '${1:l}-$((count++)).jpg'

Don’t like the single digit in the output filename? Then use the following syntax to pad the value with up to four zeros:

count=1 ; zmv '(*)_*.jpg' '${1:l}-${(l:4::0:)$((count++))}.jpg'

The l (for left) and two sets of colons handle the padding: inside the first pair of colons is an expression the gives the number of digits to pad to, the second pair contains the pad character.

The l doesn’t only with with output, you can use it on the input side too. This example changes files with a filename of 50 characters or more:

c=1 ; zmv "${(l:50::?:)}*.log" '$((c++)).log'

The eagle-eyed will have spotted that the input term uses double-quotes. Here we’re using the shell to do the expansion to get all the files to be passed into zmv.

You can use r:expression::string: to pad on the right side. Both l and r can take an optional third argument, ie. r:expression::string1:string2: which adds a string that will be inserted before padding takes place. If string2 is too long, it is truncated.

Other Options

For basic operations, you can add the -W switch, which maps the match from the input wildcard to an equivalent wildcard in the output:

zmv -W '*.JPG' '*.jpg'

This saves the use of brackets and output variables, but is less flexible.

You can ditch the single quotes too, if you preface the call with noglob:

noglob zmv -W *.JPG *.jpg

Out of the box, zmv moves files — hence the name. But it can copy them instead, if you use the -C switch. Here’s how you could copy one file to multiple locations. Say you have a directory, GitHub, for your software projects. You can copy a top-level README.MD file to every sub-directory that contains a LICENSE file with the following:

zmv -C '(**/)LICENCE' '${1}README.MD'

Making Substitutions

So far we’ve taken the output variable $1 and made it lower case. We can also tweak it further, within the output pattern, using this syntax:

${1//pattern/replacement}

So if $1 contains a sequence of words separated by dots, you can convert those dots to dashes like this:

${1//./-}

The pattern can be any Z Shell pattern, just like the zmv input pattern, and you can use brackets to embed patterns within patterns.

A variant on this can be seen in the following example, to change spaces (in any file with a space in its name) to underscores:

zmv '* *' '${f:gs/ /_}'

The $f is another zmv-generated variable — it’s the filename that matches the entire input pattern. The s/ / _ substitutes a space for a _

. The g prefix tells zmv to perform the substitution globally, throughout the value of $f.

This line removes the first four characters from the filenames:

zmv '*' '$f[5,-1]'

$f, as we know, is the matched filename. The angle brackets contain a range of characters — a standard Z Shell subscript — for the fifth element (characters in this case) through to the last element (ie. the element at the length of the whole minus 1).

Other Z Shell Functions

Finally, if you want to see what other functions Z Shell has to offer, just check out where they’re stored by viewing $FPATH:

echo $FPATH

It’ll show something like this:

/usr/local/share/zsh/site-functions:/usr/share/zsh/site-functions:/usr/share/zsh/5.8/functions

So just enter the following command for the function list:

ls /usr/local/share/zsh/site-functions

Repeat it for the other directories in the $FPATH list.

Get the Details

You can find all the details of Z Shell’s pattern matching, substitutions and the rules that guide them here: http://zsh.sourceforge.net/Doc/Release/Expansion.html. There’s also some useful info in the comments to the zmv source code.

More Posts on the Z Shell