Почему мой bash-код не работает, когда я запускаю его с sh?

У меня есть строка кода, которая отлично работает в моем терминале:

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, но я получаю точно такую ​​же ошибку. Почему я не могу запустить этот скрипт?

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

bash в основном sh что такое C++ для C. Смотрите разницу между sh и bash.

Лучший способ гарантировать, что специфичный для bash скрипт всегда работает правильно

Лучшие практики для обоих:

  1. замещать #!/bin/sh с #!/bin/bash (или от какой другой оболочки зависит ваш скрипт).
  2. Запустите этот скрипт (и все остальные!) С ./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

Шебанг предлагает, какую оболочку система должна использовать для запуска скрипта. Это позволяет вам указать #!/usr/bin/python или же #!/bin/bash так что вам не нужно помнить, какой сценарий написан на каком языке.

Люди используют #!/bin/sh когда они используют только ограниченный набор функций (определенных стандартом POSIX) для максимальной мобильности. #!/bin/bash отлично подходит для пользовательских скриптов, использующих полезные расширения bash.

/bin/sh обычно ссылается либо на минимальную POSIX-совместимую оболочку, либо на стандартную оболочку (например, bash). Даже в последнем случае #!/bin/sh может потерпеть неудачу, потому что bash будет работать в режиме совместимости, как описано на странице руководства:

Если bash вызывается с именем sh, он пытается максимально близко имитировать поведение при запуске исторических версий sh, при этом также соответствует стандарту POSIX.

Значение sh myscript.sh

Шебанг используется только когда вы бежите ./myscript.sh, /path/to/myscript.shили когда вы удаляете расширение, поместите скрипт в каталог в вашем $PATHи просто беги myscript,

Если вы явно укажете переводчик, он будет использоваться. sh myscript.sh заставит его бежать с shнезависимо от того, что говорит Шебанг. Вот почему изменение Шебанга само по себе недостаточно.

Вы всегда должны запускать скрипт с его предпочтительным интерпретатором, поэтому предпочитайте ./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",

Окончательным допингом для этих конструкций является глава " Расширение параметров" для оболочки POSIX.

Другие вопросы по тегам