How to signify repetition in a ZMV regex


I'm trying to manually pull the digests off of Rails assets (don't ask). I was directed to ZMV for easy regex-based find/replace. But the normal {32} syntax for specifying an amount of repetition does not work:

$ zmv -n '(**/)(*)' '$1${2//-[A-Za-z0-9]\{32\}/}'

I've tried some other formats. This, for example, works, but is too greedy (it will turn image-3.png into image.png, for example):

$ zmv -n '(**/)(*)' '$1${2//-[A-Za-z0-9]##\./.}'

That double-hash syntax only showed up after a lot of Googling (I'd have expected +). But I cannot for the life of me find how to make {32} work. I tried #32#? Which appeared to work, but that's because it was reading it as (in my eyes) ?32? and that means it met anything that had a three in the digest or last character.

How do I signify character repetition in zmv?


Apparently it would help some to view filenames I'm trying to match? To be clear: my question is "how do I signify character repetition in zmv" not "how do I match these filenames" (a question I know the answer to in standard RegEx format). If it helps, here is my intended before and after:


Should become:



Because I needed to do this yesterday, I did it the long way and (as expected) it worked. I'd still like to know how to avoid it in the future. Here's the command I ended up using (I repeated my character matcher 32 times explicitly):

$ zmv '(***/)(*)' '$1${2//-[A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9]/}'


For the record, I'm using zsh on OS X. I'd imagine zmv is the same across platforms, but I couldn't say for sure.

Best Answer

Shells mostly don't provide the usual regexp syntax, but wildcard “glob” patterns. Basic shell wildcards aren't as powerful as regular expressions; for example, the regexp .* (any sequence of characters) is equivalent to the glob pattern * but the regexp a* (any sequence of a's) has no glob pattern equivalent in plain sh. See Why does my regular expression work in X but not in Y? for an overview of the main different regexp/pattern syntaxes.

Zsh has zsh extended glob patterns which provides the same expressive power as regular expressions but with different syntax. These patterns are automatically enabled in zmv and in completion functions, but elsewhere in zsh they need to be enabled explicitly with setopt extended_glob (put that in your .zshrc — the only reason it isn't the default is backward compatibility with ancient versions of zsh).

There is a repeat-N-times syntax, but it's a bit hidden, listed under globbing flags rather than under the list of operators. It's the c flag, which must be used alone, followed by the number of repetition (or two comma-separated numbers to give a range).

zmv -n '(**/)(*)' '$1${2//-[A-Za-z0-9](#c32)/}'
Related Question