当我用sh运行它时,为什么我的bash代码会失败?

Wha*_*ame 34 bash sh substitution

我有一行代码可以在我的终端中正常工作:

for i in *.mp4; do echo ffmpeg -i "$i" "${i/.mp4/.mp3}"; done
Run Code Online (Sandbox Code Playgroud)

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

#!/bin/sh
for i in *.mp4; do echo ffmpeg -i "$i" "${i/.mp4/.mp3}"; done
Run Code Online (Sandbox Code Playgroud)

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

$ sh myscript.sh
myscript.sh: 2: myscript.sh: Bad substitution
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*ann 60

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
Run Code Online (Sandbox Code Playgroud)

readlink -f $(which sh)基本上#!/bin/sh是C++到C的内容.请参阅sh和bash之间的区别.

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

最好的做法是两个:

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

这是一个例子:

$ 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
Run Code Online (Sandbox Code Playgroud)

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

的含义 #!/usr/bin/python

shebang建议系统应该使用哪个shell来运行脚本.这允许您指定#!/bin/bash#!/bin/sh不必记住哪种脚本是用哪种语言编写的.

人们#!/bin/bash只使用一组有限的功能(由POSIX标准定义)才能实现最大的可移植性./bin/sh对于利用有用的bash扩展的用户脚本来说非常好.

#!/bin/sh通常符合链接到最小的POSIX兼容shell或标准shell(例如bash).即使在后一种情况下,bash也可能会失败,因为sh myscript.shwil会在兼容模式下运行,如联机帮助页中所述:

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

的含义 ./myscript.sh

当您运行的家当仅使用/path/to/myscript.sh,$PATH或当你放下的延长,把脚本在你的目录myscript,只是运行sh myscript.sh.

如果明确指定解释器,则将使用该解释器.无论shebang说什么,sh都会迫使它继续运行./myscript.sh.这就是为什么改变shebang本身是不够的.

您应该始终使用其首选解释器运行脚本,因此"$i"无论何时执行任何脚本,首选或类似.

对脚本的其他建议更改:

  • 引用变量($i而不是"${i%.mp4}.mp3")引用被认为是一种好习惯.如果存储的文件名包含空格字符,则引用的变量将防止出现问题.
  • 我喜欢你使用高级参数扩展.我建议使用"${i/.mp4/.mp3}"(而不是${parameter%word}),因为foo.mp4.backup只在末尾替换(例如一个名为的文件bash).


Jen*_*ens 10

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

#!/bin/sh
for i in *.mp4; do 
    ffmpeg -i "$i" "${i%.mp4}.mp3"
done
Run Code Online (Sandbox Code Playgroud)

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

这些结构的权威性问题是关于POSIX shell的参数扩展的章节.