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

Is it possible to do something like the following?

g=$f:s/$in/$out/;

or

g=$g:s/$in[$i]/$out[$i]/;

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)
    n=${#out[@]}

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

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

            print '$f --> $g'
            mv $f $g
        end
    } 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:

s/l/r[/]

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

So

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

  pattern=${glob//'*'/'(*)'}
  replacement=${replacement//(#m)'*'/$match[$((++n))]}

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

Then:

$ 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.