mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-09-01 09:41:44 +02:00
11196: zmv enhancements
This commit is contained in:
parent
0a463ab3e6
commit
b9ef0282d2
2 changed files with 108 additions and 16 deletions
|
@ -1,5 +1,11 @@
|
|||
2000-05-05 Peter Stephenson <pws@cambridgesiliconradio.com>
|
||||
|
||||
* pws: 11196: Functions/Misc/zmv: allow (**/) to map to a
|
||||
parameter in the obvious way; allow automatic recognition of
|
||||
wildcards with -w flag; turn off glob qualifiers by default, use
|
||||
-Q to turn on; fix bug with empty match eliding a positional
|
||||
parameter; fix bug that empty `to' pattern wasn't picked up.
|
||||
|
||||
* pws: unposted: fixes for Etc/CONTRIBUTORS based on suggestions
|
||||
in 11187 and 11197
|
||||
|
||||
|
|
|
@ -1,6 +1,30 @@
|
|||
# function zmv {
|
||||
# zmv, zcp, zln:
|
||||
#
|
||||
# This is a multiple move based on zsh pattern matching. To get the full
|
||||
# power of it, you need a postgraduate degree in zsh. However, simple
|
||||
# tasks work OK, so if that's all you need, here are some basic examples:
|
||||
# zmv '(*).txt' '$1.lis'
|
||||
# Rename foo.txt to foo.lis, etc. The parenthesis is the thing that
|
||||
# gets replaced by the $1 (not the `*', as happens in mmv, and note the
|
||||
# `$', not `=', so that you need to quote both words).
|
||||
# zmv '(**/)(*).txt '$1$2.lis'
|
||||
# The same, but scanning through subdirectories. The $1 becomes the full
|
||||
# path. Note that you need to write it like this; you can't get away with
|
||||
# '(**/*).txt'.
|
||||
# zmv -w '**/*.txt' '$1$2.lis'
|
||||
# This is the lazy version of the one above; zsh picks out the patterns
|
||||
# for you. The catch here is that you don't need the / in the replacement
|
||||
# pattern. (It's not really a catch, since $1 can be empty.)
|
||||
# zmv -C '**/(*).txt' ~/save/'$1'.lis
|
||||
# Copy, instead of move, all .txt files in subdirectories to .lis files
|
||||
# in the single directory `~/save'. Note that the ~ was not quoted.
|
||||
# You can test things safely by using the `-n' (no, not now) option.
|
||||
# Clashes, where multiple files are renamed or copied to the same one, are
|
||||
# picked up.
|
||||
#
|
||||
# Here's a more detailed description.
|
||||
#
|
||||
# Use zsh pattern matching to move, copy or link files, depending on
|
||||
# the last two characters of the function name. The general syntax is
|
||||
# zmv '<inpat>' '<outstring>'
|
||||
|
@ -8,15 +32,35 @@
|
|||
# immediate expansion, while <outstring> is a string that will be
|
||||
# re-evaluated and hence may contain parameter substitutions, which should
|
||||
# also be quoted. Each set of parentheses in <inpat> (apart from those
|
||||
# around glob qualifiers and globbing flags) may be referred to by a
|
||||
# positional parameter in <outstring>, i.e. the first (...) matched is
|
||||
# given by $1, and so on. For example,
|
||||
# around glob qualifiers, if you use the -Q option, and globbing flags) may
|
||||
# be referred to by a positional parameter in <outstring>, i.e. the first
|
||||
# (...) matched is given by $1, and so on. For example,
|
||||
# zmv '([a-z])(*).txt' '${(U)1}$2.txt'
|
||||
# renames algernon.txt to Algernon.txt, boris.txt to Boris.txt and so on.
|
||||
# The original file matched can be referred to as $f in the second
|
||||
# argument; accidental or deliberate use of other parameters is at owner's
|
||||
# risk and is not covered by the (non-existent) guarantee.
|
||||
#
|
||||
# As usual in zsh, /'s don't work inside parentheses. There is a special
|
||||
# case for (**/) and (***/): these have the expected effect that the
|
||||
# entire relevant path will be substituted by the appropriate positional
|
||||
# parameter.
|
||||
#
|
||||
# There is a shortcut avoiding the use of parenthesis with the option -w
|
||||
# (with wildcards), which picks out any expressions `*', `?', `<range>'
|
||||
# (<->, <1-10>, etc.), `[...]', possibly followed by `#'s, `**/', `***/', and
|
||||
# automatically parenthesises them. (You should quote any ['s or ]'s which
|
||||
# appear inside [...] and which do not come from ranges of the form
|
||||
# `[:alpha:]'.) So for example, in
|
||||
# zmv -w '[[:upper:]]*' '${(L)1}$2'
|
||||
# the $1 refers to the expression `[[:upper:]]' and the $2 refers to
|
||||
# `*'. Thus this finds any file with an upper case first character and
|
||||
# renames it to one with a lowercase first character. Note that any
|
||||
# existing parentheses are active, too, so you must count accordingly.
|
||||
# Furthermore, an expression like '(?)' will be rewritten as '((?))' --- in
|
||||
# other words, parenthesising of wildcards is independent of any existing
|
||||
# parentheses.
|
||||
#
|
||||
# Any error --- a substitution resulted in an empty string, a
|
||||
# substitution did not change the file name, two substitutions gave the
|
||||
# same result, the destination was an existing regular file and -f was not
|
||||
|
@ -30,7 +74,9 @@
|
|||
# to execute it. Y or y will execute it, anything else will skip it.
|
||||
# Note that you just need to type one character.
|
||||
# -n no execution: print what would happen, but don't do it.
|
||||
# -q don't allow bare glob qualifiers in the filename pattern, see below.
|
||||
# -q Turn bare glob qualifiers off: now assumed by default, so this
|
||||
# has no effect.
|
||||
# -Q Force bare glob qualifiers on.
|
||||
# -s symbolic, passed down to ln; only works with zln or z?? -L.
|
||||
# -v verbose: print line as it's being executed.
|
||||
# -o <optstring>
|
||||
|
@ -41,6 +87,8 @@
|
|||
# Call <program> instead of cp, ln or mv. Whatever it does, it should
|
||||
# at least understand the form '<program> -- <oldname> <newname>',
|
||||
# where <oldname> and <newname> are filenames generated.
|
||||
# -w Pick out wildcard parts of the pattern, as described above, and
|
||||
# implicitly add parentheses for referring to them.
|
||||
# -C
|
||||
# -L
|
||||
# -M Force cp, ln or mv, respectively, regardless of the name of the
|
||||
|
@ -48,15 +96,17 @@
|
|||
#
|
||||
# Bugs:
|
||||
# Parenthesised expressions can be confused with glob qualifiers, for
|
||||
# example a trailing '(*)' is treated as a glob qualifier. Use -q to
|
||||
# turn off glob qualifiers, or (yuk) add a suitable dummy qualifier
|
||||
# (e.g. `(.)') or dummy pattern (e.g. `(|)') at the end.
|
||||
# example a trailing '(*)' would be treated as a glob qualifier in
|
||||
# ordinary globbing. This has proved so annoying that glob qualifiers
|
||||
# are now turned off by default. To force the use of glob qualifiers,
|
||||
# give the flag -Q.
|
||||
#
|
||||
# The second argument is re-evaluated in order to expand the parameters,
|
||||
# so quoting may be a bit haphazard. In particular, a double quote
|
||||
# will need an extra level of quoting.
|
||||
#
|
||||
# The pattern is always treated as an extendedglob pattern.
|
||||
# The pattern is always treated as an extendedglob pattern. This
|
||||
# can also be interpreted as a feature.
|
||||
#
|
||||
# Unbugs:
|
||||
# You don't need braces around the 1 in expressions like '$1t' as
|
||||
|
@ -67,12 +117,13 @@ emulate -L zsh
|
|||
setopt extendedglob
|
||||
|
||||
local f g args match mbegin mend files action myname tmpf opt exec
|
||||
local opt_f opt_i opt_n opt_q opt_s opt_M opt_C opt_L opt_o opt_p
|
||||
local pat repl errstr
|
||||
local opt_f opt_i opt_n opt_q opt_Q opt_s opt_M opt_C opt_L
|
||||
local opt_o opt_p opt_v opt_w MATCH MBEGIN MEND
|
||||
local pat repl errstr fpat
|
||||
typeset -A from to
|
||||
integer stat
|
||||
|
||||
while getopts ":o:p:MCLfinqsv" opt; do
|
||||
while getopts ":o:p:MCLfinqQsvw" opt; do
|
||||
if [[ $opt = "?" ]]; then
|
||||
print -P "%N: unrecognized option: -$OPTARG" >&2
|
||||
return 1
|
||||
|
@ -81,15 +132,20 @@ while getopts ":o:p:MCLfinqsv" opt; do
|
|||
done
|
||||
(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
|
||||
|
||||
[[ -n $opt_q ]] && setopt nobareglobqual
|
||||
[[ -z $opt_Q ]] && setopt nobareglobqual
|
||||
[[ -n $opt_M ]] && action=mv
|
||||
[[ -n $opt_C ]] && action=cp
|
||||
[[ -n $opt_L ]] && action=ln
|
||||
[[ -n $opt_p ]] && action=$opt_p
|
||||
|
||||
if (( $# != 2 )); then
|
||||
print -P "Usage: %N oldpattern newpattern
|
||||
e.g. %N '(*).lis' '\$1.txt'" >&2
|
||||
print -P "Usage:
|
||||
%N oldpattern newpattern
|
||||
where oldpattern contains parenthesis surrounding patterns which will
|
||||
be replaced in turn by $1, $2, ... in newpattern. For example,
|
||||
%N '(*).lis' '\$1.txt'
|
||||
renames 'foo.lis' to 'foo.txt', 'my.old.stuff.lis' to 'my.old.stuff.txt',
|
||||
and so on." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
@ -118,7 +174,28 @@ if [[ -n $opt_s && $action != ln ]]; then
|
|||
return 1
|
||||
fi
|
||||
|
||||
files=(${~pat})
|
||||
if [[ -n $opt_w ]]; then
|
||||
# Parenthesise all wildcards.
|
||||
local newpat
|
||||
# Well, this seems to work.
|
||||
# The tricky bit is getting all forms of [...] correct, but as long
|
||||
# as we require inactive bits to be backslashed its not so bad.
|
||||
newpat="${pat//\
|
||||
(#m)(\*\*#\/|[*?]|\<[0-9]#-[0-9]#\>|\[(\[:[a-z]##:\]|\\\[|\\\]|[^\[\]]##)##\])\##\
|
||||
/($MATCH)}"
|
||||
if [[ $newpat = $pat ]]; then
|
||||
print -P "%N: warning: no wildcards were found" >&2
|
||||
else
|
||||
pat=$newpat
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ $pat = (#b)(*)\((\*\*##/)\)(*) ]]; then
|
||||
fpat="$match[1]$match[2]$match[3]"
|
||||
else
|
||||
fpat=$pat
|
||||
fi
|
||||
files=(${~fpat})
|
||||
|
||||
if [[ -o bareglobqual && $pat = (#b)(*)\([^\)\|\~]##\) ]]; then
|
||||
# strip off qualifiers for use as ordinary pattern
|
||||
|
@ -128,8 +205,16 @@ fi
|
|||
errs=()
|
||||
|
||||
for f in $files; do
|
||||
if [[ $pat = (#b)(*)\(\*\*##/\)(*) ]]; then
|
||||
# This looks like a recursive glob. This isn't good enough,
|
||||
# because we should really enforce that $match[1] and $match[2]
|
||||
# don't match slashes unless they were explicitly given. But
|
||||
# it's a start. It's fine for the classic case where (**/) is
|
||||
# at the start of the pattern.
|
||||
pat="$match[1](*/|)$match[2]"
|
||||
fi
|
||||
[[ -e $f && $f = (#b)${~pat} ]] || continue
|
||||
set -- $match
|
||||
set -- "$match[@]"
|
||||
eval g=\"$repl\"
|
||||
if [[ -z $g ]]; then
|
||||
errs=($errs "$f expanded to empty string")
|
||||
|
@ -151,6 +236,7 @@ if (( $#errs )); then
|
|||
fi
|
||||
|
||||
for f in $files; do
|
||||
[[ -z $to[$f] ]] && continue
|
||||
exec=($action ${=opt_o} $opt_s -- $f $to[$f])
|
||||
[[ -n $opt_i$opt_n$opt_v ]] && print -- $exec
|
||||
if [[ -n $opt_i ]]; then
|
||||
|
|
Loading…
Reference in a new issue