【问题标题】:Why does my Bash code fail when I run it with 'sh'?为什么我的 Bash 代码在我使用 'sh' 运行时会失败?
【发布时间】:2025-12-09 03:15:01
【问题描述】:

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

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,但我得到了完全相同的错误。为什么我不能运行这个脚本?

【问题讨论】:

  • 您是在脚本中使用#!/bin/sh 还是#!/bin/bash?
  • 在控制台中,命令由bash shell 执行。如果您在脚本中使用#!/bin/sh,则sh shell 试图执行并因此出现错误。
  • 我在第一行写了#!/bin/sh...
  • ${foo/bar/baz} 不是 POSIX,因此可能不适用于您的特定 /bin/sh。请改用#!/bin/bash。 @MichaWiedenmann 应该建议作为答案。
  • 好的,如果我用 bash 而不是 sh 执行它,它就可以工作。

标签: bash sh substitution


【解决方案1】:

${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"

这些构造的最终决定是Parameter Expansion for the POSIX shell 的章节。

【讨论】:

    【解决方案2】:

    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
    

    Difference between sh and Bash。要了解您使用的是哪个 sh:readlink -f $(which sh)

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

    最佳做法是两者

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

    这是一个例子:

    $ 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
    

    (相关:Why ./ in front of scripts?

    #!/bin/sh的含义

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

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

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

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

    sh myscript.sh的含义

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

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

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

    对脚本的其他建议更改:

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

    【讨论】:

    • +1 - 它还取决于它的执行方式,即你不能声明为#!/bin/bash 然后用sh 调用或声明为#!/bin/sh 并用bash 调用,至少在Debian 7 上你会看到两者都有错误。声明需要与调用相匹配。
    • 我认为使用#!/usr/bin/env bash 会更好。
    • 要找出您正在使用的 sh:readlink -f $(which sh)
    • @hxysayhi 我将您的评论移至答案。如果您可以看到任何内容,请随时更新/改进答案,这有助于进一步的读者。
    最近更新 更多