我还没有找到使用 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 '...' _ {} +
),也可以将列表传递给另一个工具。适当的解决方案取决于您的实际需求。
直接处理
这里我只是打印了被
>>
...<<
符号包围的文件名。构造从变量的前面删除尽可能${f##*/}
多的模式匹配(即它实现并删除路径名组件),并且返回(新的)的值而不带尾随(请记住,它是循环变量,遍历由 生成的一组文件名):*/
$f
basename
${f%.mp3}
$f
.mp3
$f
find
find . -name '*.mp3' -exec sh -c 'for f in "$@"; do f=${f##*/}; printf ">> %s <<\n" "${f%.mp3}"; done' _ {} +
过滤
在这里,我们可以通过从每行输出中
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
让我添加一个答案,仅解释观察到的结果:
$(basename "{}" ".mp3")
首先由 shell 展开- 由于
basename "{}" ".mp3"
返回{}
,实际执行的完整命令find
将是find . -name "*.mp3" -exec ls {} \;
- 因此,
ls X
对于返回的每个 X 都会执行find . -name "*.mp3"