如何在 find -exec 命令中去除带有基本名称的后缀?

如何在 find -exec 命令中去除带有基本名称的后缀?

我还没有找到使用 find 执行以下示例的方法:

$ find . -name "*.mp3" -exec echo  $(basename "{}" ".mp3") \; 

我期望该basename命令能够删除扩展。但是它没有。例如:

$ find . -name "*.mp3" -exec ls  $(basename "{}" ".mp3") \;
'Soul Man.mp3'
'Hold On! I'\''m A Comin'\''.mp3'

我也希望有人能解释一下这里发生了什么?有人会认为结果会更像这样:

ls: cannot access 'Soul Man': No such file or directory

有没有有效的方法可以删除命令字符串中的扩展find-exec

答案1

find生成文件列表。您可以直接在命令中处理这些文件(通常使用-exec sh -c '...' _ {} +),也可以将列表传递给另一个工具。适当的解决方案取决于您的实际需求。

  1. 直接处理

    这里我只是打印了被>>...<<符号包围的文件名。构造从变量的前面删除尽可能${f##*/}多的模式匹配(即它实现并删除路径名组件),并且返回(新的)的值而不带尾随(请记住,它是循环变量,遍历由 生成的一组文件名):*/$fbasename${f%.mp3}$f.mp3$ffind

    find . -name '*.mp3' -exec sh -c 'for f in "$@"; do f=${f##*/}; printf ">> %s <<\n" "${f%.mp3}"; done' _ {} +
    
  2. 过滤

    在这里,我们可以通过从每行输出中find删除尾随字符来过滤输出。有两种变体;第一种使用 GNU 扩展来处理可能包含非打印字符的文件名,例如.mp3新队;第二种使用更标准的工具,但它可以处理的文件名集更有限:

    # NUL-terminated file names
    find . -name '*.mp3' -print0 | sed -Ez 's!^.*/(.*)\.mp3$!\1!'
    

    您将获得一组以 NUL 而不是换行符结尾的文件名。进行筛选tr '\0' '\n'以直观地查看输出。

    # Newline-terminated file names
    find . -name '*.mp3' -print | sed 's!^.*/(.*)\.mp3$!\1!'
    

现在解释一下您自己的代码不起作用的原因:

find . -name "*.mp3" -exec ls -d $(basename "{}" ".mp3") \;

您没有引用传递给的段-exec,因此 shell 会获取它的执行是find其正常解析和变量插值的一部分。具体来说,$(basename "{}" ".mp3")在命令运行之前,对 进行一次评估。

basename "{}" ".mp3" 
{}

因此您的find实际上是这个命令(我保留了前面\的,;以便您可以复制并粘贴它;实际上它也会被删除,并且;逐字传递给find):

find . -name "*.mp3" -exec ls {} \;

您可以像往常一样将字符串放在单引号中来避免执行前评估:

find . -name "*.mp3" -exec 'ls $(basename "{}" ".mp3")' \;

但是,这在当前目录的任何子目录中都会失败,因为basename 根据定义删除路径部分。

一般来说,您应该考虑ls将其作为视觉参考工具。对于文件/目录名称的程序化使用,通常有更好的替代方案,选择正确的替代方案取决于实际目标。

答案2

单程:

find . -name '*.mp3' -exec bash -c 'for arg; do echo "${arg%.*}"; done' bash {} +

答案3

让我添加一个答案,仅解释观察到的结果:

  1. $(basename "{}" ".mp3")首先由 shell 展开
  2. 由于basename "{}" ".mp3"返回{},实际执行的完整命令find将是find . -name "*.mp3" -exec ls {} \;
  3. 因此,ls X对于返回的每个 X 都会执行find . -name "*.mp3"

相关内容