为什么我的 bash 代码在使用 sh 运行时会失败?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15179446/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 23:24:41  来源:igfitidea点击:

Why does my bash code fail when I run it with sh?

bashshsubstitution

提问by WhatIsName

I have a line of code that works fine in my terminal:

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

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

Then I put the exact same line of code in a script myscript.sh:

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

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

However, now I get an error when running it:

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

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

Based on other questions I tried changing the shebang to #!/bin/bash, but I get the exact same error. Why can't I run this script?

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

回答by Micha Wiedenmann

TL;DR: Since you are using bashspecific features, your script has to run with bashand not with sh:

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

See Difference between sh and bash. To find out which sh you are using: readlink -f $(which sh).

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

The best way to ensure a bash specific script always runs correctly

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

The best practices are to both:

最好的做法是两个

  1. Replace #!/bin/shwith #!/bin/bash(or whichever other shell your script depends on).
  2. Run this script (and all others!) with ./myscript.shor /path/to/myscript.sh, without a leading shor bash.
  1. 替换#!/bin/sh#!/bin/bash(或您的脚本所依赖的任何其他 shell)。
  2. 使用./myscript.shor运行此脚本(以及所有其他脚本!)/path/to/myscript.sh,不带前导shbash

Here's an example:

下面是一个例子:

$ 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

(Related: Why ./ in front of scripts?)

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

The meaning of #!/bin/sh

的意思 #!/bin/sh

The shebang suggests which shell the system should use to run a script. This allows you to specify #!/usr/bin/pythonor #!/bin/bashso that you don't have to remember which script is written in what language.

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

People use #!/bin/shwhen they only use a limited set of features (defined by the POSIX standard) for maximum portability. #!/bin/bashis perfectly fine for user scripts that take advantage of useful bash extensions.

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

/bin/shis usually symlinked to either a minimal POSIX compliant shell or to a standard shell (e.g. bash). Even in the latter case, #!/bin/shmay fail because bashwil run in compatibility mode as explained in the manpage:

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

If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.

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

The meaning of sh myscript.sh

的意思 sh myscript.sh

The shebang is only used when you run ./myscript.sh, /path/to/myscript.sh, or when you drop the extension, put the script in a directory in your $PATH, and just run myscript.

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

If you explicitly specify an interpreter, that interpreter will be used. sh myscript.shwill force it to run with sh, no matter what the shebang says. This is why changing the shebang is not enough by itself.

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

You should always run the script with its preferred interpreter, so prefer ./myscript.shor similar whenever you execute any script.

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

Other suggested changes to your script:

对脚本的其他建议更改:

  • It is considered good practice to quote variables ("$i"instead of $i). Quoted variables will prevent problems if the stored file name contains white space characters.
  • I like that you use advanced parameter expansion. I suggest to use "${i%.mp4}.mp3"(instead of "${i/.mp4/.mp3}"), since ${parameter%word}only substitutes at the end (for example a file named foo.mp4.backup).
  • 引用变量("$i"而不是$i)被认为是一种很好的做法。如果存储的文件名包含空格字符,引用的变量将防止出现问题。
  • 我喜欢你使用高级参数扩展。我建议使用"${i%.mp4}.mp3"(而不是"${i/.mp4/.mp3}"),因为${parameter%word}只在末尾替换(例如名为 的文件foo.mp4.backup)。

回答by Jens

The ${var/x/y/}construct is not POSIX. In Your case, where you just remove a string at the end of a variable and tack on another string, the portable POSIX solution is to use

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

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

or even shorter, ffmpeg -i "$i" "${i%4}3".

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

The definitive dope for these constructs is the chapter on Parameter Expansion for the POSIX shell.

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