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

为什么我的bash代码在使用sh运行时失败?,bash,sh,substitution,Bash,Sh,Substitution,我有一行代码可以在我的终端中正常工作: 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 $

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

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

$ bash myscript.sh
ffmpeg -i bar.mp4 bar.mp3
ffmpeg -i foo.mp4 foo.mp3
但是,现在运行时出现错误:

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

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

TL;DR:由于您使用的是
bash
特定功能,因此您的脚本必须与
bash
一起运行,而不是与
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

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

看。要了解您使用的是哪种sh:
readlink-f$(哪种sh)

确保特定于bash的脚本始终正确运行的最佳方法 最佳做法包括:

  • 替换
    #/bin/sh
    #/bin/bash
    (或脚本所依赖的任何其他shell)
  • 使用
    /myscript.sh
    /path/to/myscript.sh
    运行此脚本(以及所有其他!),不使用前导的
    sh
    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
    只有当您运行
    /myscript.sh
    /path/to/myscript.sh
    ,或者当您删除扩展名时,将脚本放在
    $path
    的目录中,然后只运行
    myscript
    时,才会使用shebang

    如果显式指定解释器,则将使用该解释器
    shmyscript.sh
    将强制它与
    sh
    一起运行,不管shebang怎么说。这就是为什么仅仅改变shebang是不够的

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

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

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


    关于这些构造的最终描述是关于的章节。

    您使用的是#/垃圾箱/垃圾箱或#/bin/bash在控制台中的脚本中,命令由
    bash
    shell执行。如果您使用的是
    #/bin/sh
    在您的脚本中,它是
    sh
    shell试图执行的,因此出现了错误/第一行中的bin/sh…${foo/bar/baz}不是POSIX,因此可能无法使用特定的/bin/sh。请使用
    #/而是bin/bash
    @MichaWiedenmann应该建议作为一个答案。好的,如果我用bash而不是sh来执行它,它会工作。+1-它还取决于它的执行方式,也就是说,你不能声明为
    #/bin/bash
    然后用
    sh
    调用或声明为
    #/bin/sh
    并使用
    bash
    调用,至少在Debian 7上,您会看到两者都有错误。声明需要与调用匹配。我认为使用
    #/usr/bin/env bash
    会是一个更好的方法。要找出您正在使用的sh:
    readlink-f$(哪个sh)
    @hxysayhi,我将您的评论移到了答案上。如果您能看到任何有助于进一步阅读的内容,请随时更新/改进答案。