我尝试使用 bash 将 mp4 文件转换为 mp3:
IFS=$'\n'
for f in $(ls -1 --quoting-style=escape -R ./*.mp4); do ffmpeg -i "$f" "${f%.mp4}.mp3"; done
并且,ffmpeg 抛出一个错误:./Asu\ no\ Yozora\ Shoukaihan.mp4: No such file or directory
但是如果我尝试手动输入它,它会正常工作,有人知道它为什么会这样吗?
我尝试使用 bash 将 mp4 文件转换为 mp3:
IFS=$'\n'
for f in $(ls -1 --quoting-style=escape -R ./*.mp4); do ffmpeg -i "$f" "${f%.mp4}.mp3"; done
并且,ffmpeg 抛出一个错误:./Asu\ no\ Yozora\ Shoukaihan.mp4: No such file or directory
但是如果我尝试手动输入它,它会正常工作,有人知道它为什么会这样吗?
这是因为转义首先不是为 ffmpeg 准备的 – 它是为 shell 准备的。shell 的工作是将命令拆分为“单词”(此时它会处理引用/转义并扩展通配符),而实际的程序(如 ffmpeg)只希望在其 argv[] 中接收“干净”的值。
您提到的区别是因为在读取命令输入时处理引用和转义:普通空格被视为单词分隔符,但转义
\
在读取时立即变为普通空格 -不是在变量扩展后的某个时间点,而是尽早。例如,如果您有一个类似
file=One\ two\ three.mp4
或 equal 的赋值file="One two three.mp4"
,那么反斜杠(或引号)将在输入解析时处理,不会成为值的一部分。这意味着,如果您以某种方式在变量的值中包含反斜杠转义,则在扩展该变量时不会执行额外的“后期”取消转义。(同样,如果你运行,
ffmpeg -i "One two.mkv" One\ two.avi
这会立即变成四个“单词”,所有引号都被删除:ffmpeg
,,,,,所以 ffmpeg 永远不会处理反斜杠。)-i
One two.mkv
One two.avi
这就是尝试使用时出现的问题
$(ls -Q)
(也是它与直接输入之间的差异的原因),因为 $() 扩展(或任何其他扩展)的结果仅经过 IFS 的分词,而不会取消引用,因此最终得到的f
值中包含文字反斜杠。当"$f"
稍后扩展时,同样也不会取消引用其值,因此您仍然拥有包含文字反斜杠的参数。通常的建议是不要以任何方式、形状或形式使用
for $(ls)
。如前所述,是 shell 扩展了通配符,因此for f in $(ls ./*.mp4)
最好将基本通配符写成直接通配符for f in ./*.mp4
- 无需与 IFS 混淆 - 如果您需要递归扩展,那么 Bash 提供了“globstar”选项:而且,如果您绝对必须使用的输出
ls --quoting...
或某些输出 shell 引用值的其他程序,则需要使用例如"${var@E}"
或某些谨慎使用eval
(最好不要)明确取消引用这些值。