如何在 ZMV 正则表达式中表示重复?

如何在 ZMV 正则表达式中表示重复?

我正在尝试手动从 Rails 资源中提取摘要(不要问)。我被引导到 ZMV 进行基于正则表达式的轻松查找/替换。但{32}指定重复次数的正常语法不起作用:

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

我尝试过其他格式。例如,这个可以工作,但太贪婪了(例如,它会变成)image-3.pngimage.png

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

经过大量的 Google 搜索后,才发现这种双重哈希语法(我本以为是+)。但我无论如何也找不到如何让它{32}工作的方法。我试过#32#?它似乎有效,但那是因为它将其读取为(在我看来)?32?,这意味着它遇到了摘要或最后一个字符中包含 3 的任何内容。

如何在 zmv 中表示字符重复?

编辑:

显然,查看我试图匹配的文件名会有所帮助?明确地说:我的问题是“如何在 zmv 中表示字符重复” 不是“我如何匹配这些文件名”(我知道标准 RegEx 格式的答案)。如果有帮助,以下是我预期的前后对比:

directory/asset-jej4jtifne9bjkkeuwr09rewrewlur23.css
another-directory/style-748reiodlpqwerntaerwerwerexfzsdf.js.gz
directory/subdirectory/this-is-a-thing-qwertyuiopasdfghjklzxcvbnm123456.js
third-directory/should-not-match-3.css

应变为:

directory/asset.css
another-directory/style.js.gz
directory/subdirectory/this-is-a-thing.js
third-directory/should-not-match-3.css

第二次编辑:

因为昨天需要这样做,所以我用很长的路走,而且(正如预期的那样)成功了。我仍然想知道将来如何避免这种情况。这是我最终使用的命令(我明确重复了我的字符匹配器 32 次):

$ 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]/}'

第三次编辑:

为了记录,我在 OS X 上使用 zsh。我想象 zmv 在各个平台上是相同的,但我不能肯定。

答案1

Shell 大多不提供常用的正则表达式语法,而是提供通配符“glob”模式。基本 Shell 通配符不如正则表达式强大;例如,正则表达式.*(任何字符序列)相当于 glob 模式*,但正则表达式a*(任何 's 序列a)在普通 sh 中没有等同于 glob 模式。请参阅为什么我的正则表达式在 X 中有效,但在 Y 中无效?了解主要的不同正则表达式/模式语法的概述。

Zsh 有zsh 扩展 glob 模式它提供与正则表达式相同的表达能力,但语法不同。这些模式在zmv和完成函数中自动启用,但在 zsh 的其他地方,它们需要使用显式启用setopt extended_glob(将其放在您的.zshrc- 它不是默认设置的唯一原因是与旧版本的 zsh 向后兼容)。

有一个重复 N 次的语法,但它有点隐蔽,列在通配符而不是在操作符列表下。它是c标志,必须单独使用,后跟重复次数(或两个逗号分隔的数字以给出范围)。

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

答案2

我无法让它工作zmv。一定有办法,但我记不清了。然而,zmv并不是唯一可以做这样的事情的工具。你也可以使用rename

  • 如果你使用zsh

    $ rename -n 's/-[A-Za-z0-9]{32}//' **/* 
    another-directory/style-748reiodlpqwerntaerwerwerexfzsdf.js.gz renamed as another-directory/style.js.gz
    directory/asset-jej4jtifne9bjkkeuwr09rewrewlur23.css renamed as directory/asset.css
    directory/subdirectory/this-is-a-thing-qwertyuiopasdfghjklzxcvbnm123456.js renamed as directory/subdirectory/this-is-a-thing.js
    
  • 如果你使用bash

    $ shopt -s globstar 
    $ rename -n 's/-[A-Za-z0-9]{32}//' **/* 
    another-directory/style-748reiodlpqwerntaerwerwerexfzsdf.js.gz renamed as another-directory/style.js.gz
    directory/asset-jej4jtifne9bjkkeuwr09rewrewlur23.css renamed as directory/asset.css
    directory/subdirectory/this-is-a-thing-qwertyuiopasdfghjklzxcvbnm123456.js renamed as directory/subdirectory/this-is-a-thing.js
    

请注意,Linux 世界中有两个rename命令。上面的示例使用 Perl 命令,这是基于 Debian 的发行版中的默认命令。


您无法使其工作的原因zmv是:i)它不是zmv解释表达式,这是一个 shell 特性;因此 ii)这根本不是一个正则表达式,它是一个 glob。

当您运行问题中的命令时,zmv设置$2为每个文件名,然后由 shell 运行替换 ( ${2//...)。一旦变量被 shell 扩展,它就会返回到zmv尝试重命名操作的位置。

与 korn shell 和 bash 类似,zsh支持格式将从变量中${foo//bar}删除 glob 的所有匹配项(与仅删除第一个匹配项的格式相反)。它的工作原理如下:bar$foo${foo/bar}

% foo="Xababab"
% echo ${foo//ab}
X
% echo ${foo//a*b}
X

如上所示,这些模式是 glob,而不是正则表达式。全局 a*b表示“匹配a,然后是 0 个或多个字符,然后是b”。它相当于这个正则表达式:a.*b与正则表达式不同,globs 不支持重复(显然,zsh 的 glob 可以,参见Gilles 的回答)。x{n}语法将不会匹配 n 次重复的 x。因此,这就是您的正则表达式失败的原因:它根本没有被解释为正则表达式!

相关内容