我对 Bash 比较陌生,我正在尝试做一些表面上看起来非常简单的事情 - 在目录层次结构上运行 find 来获取所有 *.wma 文件,将输出通过管道传输到一个命令,在其中将它们转换为 mp3 并将转换后的文件另存为 .mp3。我的想法是该命令应该如下所示(我已经放弃了音频转换命令,而是使用 echo 进行说明):
$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3
据我了解,-print0 arg将让我处理带有空格的文件名(其中许多都是因为它们是音乐文件)。然后我期望(由于 xargs)find 中的每个文件路径都在 f 中捕获,并且使用字符串末尾的子字符串匹配/删除,我应该使用 mp3 回显原始文件路径扩展名而不是 wma。然而,我看到的不是这个结果,而是以下内容:
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
...
所以我的问题(除了特定的“我在这里做错了什么”)是这样的 - 在字符串操作操作中,作为管道操作结果的值需要与作为变量赋值结果的值进行不同的处理?
答案1
正如其他答案已经确定的那样,${f%.*}
在运行命令之前由 shell 扩展xargs
。您需要对每个文件名进行一次扩展,并将 shell 变量f
设置为文件名(传递-I f
不会这样做:没有 shell 变量的概念,它会在命令中xargs
查找字符串,所以如果您f
使用例如xargs -I e echo …
它会执行类似./somedir/somefile.wmacho .mp3
)的命令。
继续这种方法,告诉xargs
调用可以执行扩展的 shell。更好的是,告诉find
-xargs
是一个基本上过时的工具,很难正确使用,因为现代版本的 find 具有一个结构,可以完成相同的工作(甚至更多),但管道难度更少。而不是find … -print0 | xargs -0 command …
,运行find … -exec command … {} +
。
find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +
参数_
在$0
shell 中;文件名作为for f; do …
循环的位置参数传递。此命令的一个简单版本为每个文件执行一个单独的 shell,这等效但稍微慢一些:
find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;
find
假设您正在运行一个相当新的 shell(ksh93、bash ≥4.0 或 zsh),您实际上不需要在这里使用。在 bash 中,输入shopt -s globstar
your.bashrc
来激活**/
glob 模式以在子目录中递归(在 ksh 中,即set -o globstar
)。然后你就可以运行
for f in **/*.wma; do
echo "${f%.*}.mp3"
done
(如果您有名为 的目录*.wma
,请[ -f "$f" ] || continue
在循环的开头添加。)
答案2
如果您的解决方案评估 ${f%.*}.mp3 是在您调用整个命令的 shell 中完成的,而不是在由参数。在你的外壳里没有F变量,它被替换为空字符串。
解决方案使用参数和-如果:
% cat 1.sh
#!/bin/sh
f=$1
echo ${f%.*}.mp3
% /bin/ls -1
1.sh
aaa.wma
bbbb.wma
ccccc.wma
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f
./aaa.mp3
./ccccc.mp3
./bbbb.mp3
但我会这样做:
% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'