Zsh: Is it possible to use the value of a variable as a substitution pattern?

Is it possible to do something like the following?




I’ve tried random character combinations like ${~in} and g=$g:ps/ but, as you can see, I’m not a zsh expert — and guessing isn’t getting me anywhere 🙂 I’ve even tried reading the manpages…

EDIT: I’m trying to write my own zmv-type command:

# in   a*b* -> a b NULL     rn a*b* *c*d   aXXbYY  -->  XXcYYd
# out  *c*d -> NULL c d     rn inv* mag*   inv.ext inv.spc -->  mag.ext mag.spc

alias rn='noglob frn'

function frn() {                                                                                                   
    local in out m n f g i
    setopt localoptions glob histsubstpattern

    in=("${(@s:*:)1}")                          # split $1 and $2 on '*'
    out=("${(@s:*:)2}")                         # keeping NULL fields

    m=${#in[@]}                                 # count n(array-elements)

    if [[ $m = $n ]] {
        foreach f ($~1)                         # foreach file in globbed $1
            for (( i = 1; i < n; i += 1 )); do
                g=$g:s/$in[$i]/$out[$i]/        # subst corresponding fields

            g=$g:s/%$in[$i]/$out[$i]/           # last subst has % anchor

            print '$f --> $g'
            mv $f $g
    } else {
        print "n(*) must be same for both patterns"
Asked By: colinh


Unlike $out which is expanded, $in is treated as a literal string here… so nothing happens (unless f contained $in literally).
To expand in the left-hand side you’d need HIST_SUBST_PATTERN set:


Substitute r for l as described below.
By default the left-hand side of substitutions are not patterns, but
character strings[…]
is set, l is treated as a pattern of the usual form described in
Filename Generation.


setopt histsubstpattern

and then it should work…

Answered By: don_crissti

Your approach to try and reimplement zmv -W is very brittle.

In rfn a*b*c b*c*d applied to acrobatic which it should change to bcrocatid, you’d attempting to do

  • a -> b
  • b -> c
  • c -> d

in succession, but that would yield:

  • acrobatic -> bcrobatic
  • bcrobatic -> ccrobatic
  • ccrobatic -> dcrobatic

Just do it the same way zmv -W does by

  1. Changing the a*b*c pattern to (#b)a(*)b(*)c to enable back-references
  2. Changing the b*c*d replacement to b$match[1]c$match[2]d
  3. And perform one substitution with either the Korn-style ${var/pattern/replacement} or csh-stype $var:s/pattern/replacement with histsubstpattern or with zsh 6.0+ with the :S modifier which uses patterns with or without histsubstpattern.
function frn {
  emulate -L zsh
  set -o extendedglob
  local glob=$1 replacement=$2 n=0 file newfile


  for file ($~glob) {
    if [[ -n $newfile && $file != $newfile ]] {
      print -ru2 -- "${(q+)file} -> ${(q+)newfile}"
      # mv -- $file $newfile
alias frn='noglob frn'


$ frn a*b*c b*c*d
acrobatic -> bcrocatid

Same as:

$ alias zmvw='noglob zmv -W'
$ zmvw -n a*b*c b*c*d
mv -- acrobatic bcrocatid

Except zmv handles more wildcards than just * and does a lot more sanity checks and error handling.

Answered By: Stéphane Chazelas
Categories: Answers Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.