web-dev-qa-db-ja.com

shでbashコードを実行すると失敗するのはなぜですか?

私の端末でうまく機能するコード行があります:

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

他の質問に基づいて、シバンを#!/bin/bash、しかし、まったく同じエラーが表示されます。このスクリプトを実行できないのはなぜですか?

34
WhatIsName

TL; DR:bash固有の機能を使用しているため、スクリプトはbashではなくshで実行する必要があります。

readlink -f $(which sh)を使用します。

Bash固有のスクリプトが常に正しく実行されるようにする最善の方法

ベストプラクティスはbothです:

  1. $ 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 #!/bin/sh(またはスクリプトが依存する他のシェル)に置き換えます。
  2. #!/bin/bashまたは./myscript.shを使用して、先頭のshまたはbashなしでこのスクリプト(およびその他すべて)を実行します。

以下に例を示します。

/path/to/myscript.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 の意味

シバンは、システムがスクリプトを実行するために使用するシェルを提案します。これにより、#!/bin/shまたは#!/usr/bin/pythonを指定できるため、どのスクリプトがどの言語で書かれているかを覚えておく必要がありません。

移植性を最大限にするために、限られた機能セット(POSIX標準で定義)のみを使用する場合、人々は#!/bin/bashを使用します。 #!/bin/shは、便利なbash拡張機能を利用するユーザースクリプトに最適です。

#!/bin/bashは通常、最小限のPOSIX準拠シェルまたは標準シェル(bashなど)にシンボリックリンクされています。後者の場合でも、bashmanpage で説明されている互換モードで実行されるため、/bin/shは失敗する可能性があります。

Shという名前でbashが呼び出されると、POSIX標準にも準拠しながら、shの歴史的なバージョンの起動時の動作を可能な限り模倣しようとします。

#!/bin/shの意味

Shebangは、sh myscript.sh./myscript.shを実行するとき、または拡張機能をドロップして、スクリプトを/path/to/myscript.shのディレクトリに配置し、myscriptを実行するときにのみ使用されます。 。

インタープリターを明示的に指定すると、そのインタープリターが使用されます。 $PATHは、Shebangが何と言っても、shで強制的に実行します。これが、シバンの変更だけでは十分ではない理由です。

常に優先インタープリターでスクリプトを実行する必要があります。そのため、スクリプトを実行するときは常にsh myscript.shなどを優先してください。

スクリプトへの他の提案された変更:

  • 変数を引用することをお勧めします(./myscript.shの代わりに"$i")。保存されたファイル名に空白文字が含まれている場合、引用符で囲まれた変数は問題を防ぎます。
  • 高度な パラメータ展開 を使用するのが好きです。 $iの代わりに"${i%.mp4}.mp3"だけを使用するため、"${i/.mp4/.mp3}"の代わりに${parameter%Word}を使用することをお勧めします(たとえば、foo.mp4.backupという名前のファイル)。
60

${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シェルのパラメーター拡張 の章です。

10
Jens