36

我有一行代码在我的终端中运行良好:

for i in *.mp4; do echo ffmpeg -i "$i" "${i/.mp4/.mp3}"; done

然后我将完全相同的代码行放入脚本中myscript.sh

#!/bin/sh
for i in *.mp4; do echo ffmpeg -i "$i" "${i/.mp4/.mp3}"; done

但是,现在运行它时出现错误:

$ sh myscript.sh
myscript.sh: 2: myscript.sh: Bad substitution

基于其他问题,我尝试将shebang更改为#!/bin/bash,但我得到了完全相同的错误。为什么我不能运行这个脚本?

4

2 回答 2

62

TL;DR:由于您使用的是 Bash 特定功能,因此您的脚本必须使用 Bash 而不是sh

$ sh myscript.sh
myscript.sh: 2: myscript.sh: Bad substitution

$ bash myscript.sh
ffmpeg -i bar.mp4 bar.mp3
ffmpeg -i foo.mp4 foo.mp3

请参阅sh 和 Bash 之间的区别。要找出您正在使用的 sh readlink -f $(which sh):.

确保 bash 特定脚本始终正确运行的最佳方法

最佳做法

  1. 替换#!/bin/sh#!/bin/bash(或您的脚本所依赖的任何其他 shell)。
  2. ./myscript.sh使用or运行此脚本(以及所有其他脚本!)/path/to/myscript.sh,不带前导shor bash

这是一个例子:

$ cat myscript.sh
#!/bin/bash
for i in *.mp4
do
  echo ffmpeg -i "$i" "${i/.mp4/.mp3}"
done

$ chmod +x myscript.sh   # Ensure script is executable

$ ./myscript.sh
ffmpeg -i bar.mp4 bar.mp3
ffmpeg -i foo.mp4 foo.mp3

(相关:为什么 ./ 在脚本前面?

的意思#!/bin/sh

shebang 建议系统应该使用哪个 shell 来运行脚本。这使您可以指定#!/usr/bin/python#!/bin/bash不必记住哪个脚本是用什么语言编写的。

人们#!/bin/sh只使用一组有限的功能(由 POSIX 标准定义)以实现最大的可移植性。#!/bin/bash非常适合利用有用 bash 扩展的用户脚本。

/bin/sh通常符号链接到最小的 POSIX 兼容 shell 或标准 shell(例如 bash)。即使在后一种情况下,#!/bin/sh也可能会失败,因为bash它将在手册页中解释的兼容模式下运行:

如果使用名称 sh 调用 bash,它会尝试尽可能地模仿 sh 的历史版本的启动行为,同时也符合 POSIX 标准。

的意思sh myscript.sh

shebang 仅在您运行./myscript.sh,/path/to/myscript.sh或删除扩展名时使用,将脚本放在您的目录中$PATH,然后运行myscript​​.

如果您明确指定解释器,则将使用该解释器。无论shebang说什么,sh myscript.sh都会强制它运行。sh这就是为什么仅仅改变 shebang 是不够的。

您应该始终使用其首选解释器运行脚本,因此./myscript.sh无论何时执行任何脚本时都应首选或类似。

对脚本的其他建议更改:

  • 引用变量("$i"而不是$i)被认为是一种很好的做法。如果存储的文件名包含空格字符,带引号的变量将防止出现问题。
  • 我喜欢你使用高级参数扩展。我建议使用"${i%.mp4}.mp3"(而不是"${i/.mp4/.mp3}"),因为${parameter%word}仅在末尾替换(例如名为 的文件foo.mp4.backup)。
于 2013-03-03T12:28:28.347 回答
10

${var/x/y/}构造不是 POSIX。在您的情况下,您只需在变量末尾删除一个字符串并附加另一个字符串,便携式 POSIX 解决方案是使用

#!/bin/sh
for i in *.mp4; do
    ffmpeg -i "$i" "${i%.mp4}.mp3"
done

甚至更短,ffmpeg -i "$i" "${i%4}3".

这些构造的最终决定是关于POSIX shell 的参数扩展一章。

于 2013-03-03T17:18:57.457 回答