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