Обычно я работаю с серверами Ubuntu LTS, которые, насколько я понимаю, символическая ссылка /bin/sh
к /bin/dash
. Множество других дистрибутивов через символическую ссылку /bin/sh
к /bin/bash
.
Из этого я понимаю, что если сценарий использует #!/bin/sh
сверху он может не работать одинаково на всех серверах?
Есть ли предлагаемая практика того, какую оболочку использовать для сценариев, когда требуется максимальная переносимость этих сценариев между серверами?
Существует примерно четыре уровня переносимости сценариев оболочки (что касается строки shebang):
Самый портативный: используйте #!/bin/sh
shebang и использование только базовый синтаксис оболочки, указанный в Стандарт POSIX. Это должно работать практически в любой системе POSIX / unix / linux. (Ну, за исключением Solaris 10 и более ранних версий, у которых была настоящая устаревшая оболочка Bourne, предшествующая POSIX, поэтому она несовместима, так как /bin/sh
.)
Второй по переносимости: используйте #!/bin/bash
(или #!/usr/bin/env bash
) shebang и придерживайтесь функций bash v3. Это будет работать в любой системе, в которой есть bash (в ожидаемом месте).
Третий самый портативный: используйте #!/bin/bash
(или #!/usr/bin/env bash
) shebang и используйте возможности bash v4. Это не сработает в любой системе с bash v3 (например, macOS, которая должна использовать его по причинам лицензирования).
Наименее портативный: используйте #!/bin/sh
shebang и используйте расширения bash для синтаксиса оболочки POSIX. Это не сработает в любой системе, в которой есть что-то, кроме bash для / bin / sh (например, последние версии Ubuntu). Никогда не делай этого; это не просто проблема совместимости, это просто неправильно. К сожалению, это ошибка многих людей.
Моя рекомендация: используйте самый консервативный из первых трех, который предоставляет все функции оболочки, необходимые для сценария. Для максимальной переносимости используйте вариант №1, но, по моему опыту, некоторые функции bash (например, массивы) достаточно полезны, поэтому я выберу №2.
Худшее, что вы можете сделать, - это №4, использовать не тот шебанг. Если вы не уверены, какие функции являются базовыми POSIX, а какие - расширениями bash, либо используйте bash shebang (т.е. вариант № 2), либо тщательно протестируйте сценарий с помощью очень простой оболочки (например, dash на ваших серверах Ubuntu LTS). В вики Ubuntu есть хороший список башизмов, на которые следует обратить внимание.
В вопросе о Unix и Linux есть очень хорошая информация об истории и различиях между оболочками. "Что значит быть sh-совместимым?" и вопрос о Stackoverflow "Разница между sh и bash".
Также имейте в виду, что оболочка - не единственное, что отличает разные системы; Если вы привыкли к Linux, вы привыкли к командам GNU, которые имеют множество нестандартных расширений, которые вы можете не найти в других системах Unix (например, bsd, macOS). К сожалению, здесь нет простого правила, вам просто нужно знать диапазон вариаций для команд, которые вы используете.
Одна из самых неприятных команд с точки зрения переносимости - одна из самых простых: echo
. Каждый раз, когда вы используете его с любыми параметрами (например, echo -n
или echo -e
) или с любыми escape-символами (обратными косыми чертами) в строке для печати разные версии будут делать разные вещи. Каждый раз, когда вы хотите напечатать строку без перевода строки после нее или с escape-символами в строке, используйте printf
вместо этого (и узнайте, как это работает - это сложнее, чем echo
является). В ps
команда также беспорядок.
Еще одна общая вещь, на которую следует обратить внимание, - это недавние расширения / GNUish для синтаксиса параметров команды: старый (стандартный) формат команды заключается в том, что за командой следуют параметры (с одним тире, и каждый параметр представляет собой одну букву), за которыми следует аргументы команды. Недавние (и часто непереносимые) варианты включают длинные параметры (обычно вводятся с --
), позволяя параметрам идти после аргументов и используя --
чтобы отделить параметры от аргументов.
в ./configure
сценария, который подготавливает язык TXR к сборке, я написал следующий пролог для лучшей переносимости. Скрипт сам загрузится, даже если #!/bin/sh
это старая оболочка Bourne Shell, не соответствующая POSIX. (Я собираю каждый выпуск на ВМ Solaris 10).
#!/bin/sh
# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells
if test x$txr_shell = x ; then
for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
if test -x $shell ; then
txr_shell=$shell
break
fi
done
if test x$txr_shell = x ; then
echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
txr_shell=/bin/sh
fi
export txr_shell
exec $txr_shell $0 ${@+"$@"}
fi
# rest of the script here, executing in upgraded shell
Идея здесь в том, что мы находим лучшую оболочку, чем та, в которой мы работаем, и повторно выполняем сценарий, используя эту оболочку. В txr_shell
переменная окружения установлена, так что повторно выполненный сценарий знает, что это повторно выполненный рекурсивный экземпляр.
(В моем сценарии txr_shell
переменная также впоследствии используется ровно для двух целей: во-первых, она печатается как часть информационного сообщения в выводе скрипта. Во-вторых, он устанавливается как SHELL
переменная в Makefile
, так что make
будет использовать эту оболочку также для выполнения рецептов.)
В системе, где /bin/sh
тире, вы можете видеть, что приведенная выше логика найдет /bin/bash
и повторно выполните сценарий с этим.
На коробке Solaris 10 /usr/xpg4/bin/sh
сработает, если Bash не будет найден.
Пролог написан на консервативном диалекте оболочки с использованием test
для проверки существования файлов, а ${@+"$@"}
трюк для расширения аргументов, обслуживающих некоторые сломанные старые оболочки (которые были бы просто "$@"
если бы мы были в оболочке, соответствующей POSIX).
Все варианты языка оболочки Bourne объективно ужасны по сравнению с современными языками сценариев, такими как Perl, Python, Ruby, node.js и даже (возможно) Tcl. Если вам нужно сделать что-нибудь, даже немного сложное, вы будете счастливее, если будете использовать один из вышеперечисленных вместо сценария оболочки.
Единственное преимущество языка оболочки перед новыми языками состоит в том, что что-то называя себя /bin/sh
гарантированно существует для всего, что претендует на звание Unix. Однако это может быть даже несовместимо с POSIX; многие из устаревших проприетарных Unix заморозили язык, реализованный /bin/sh
и утилиты в PATH по умолчанию предшествующий к изменениям, потребованным Unix95 (да, Unix95, двадцать лет назад и не более). В каталоге может быть набор инструментов Unix95 или даже POSIX.1-2001, если вам повезет. не по умолчанию PATH (например, /usr/xpg4/bin
), но их существование не гарантируется.
Однако основы Perl таковы: более вероятно присутствовать в произвольно выбранной установке Unix, чем Bash. (Под "основами Perl" я подразумеваю /usr/bin/perl
существует и некоторые(возможно, довольно старая версия Perl 5, и, если вам повезет, также доступен набор модулей, поставляемых с этой версией интерпретатора.)
Следовательно:
Если вы пишете что-то, что должно работать где угодно который претендует на роль Unix (например, сценарий "configure"), вам необходимо использовать #! /bin/sh
, и вам не нужно вообще использовать какие-либо расширения. В настоящее время я бы написал оболочку, совместимую с POSIX.1-2001, в этом случае, но я был бы готов исправить POSIXisms, если бы кто-то попросил поддержки для ржавого железа.
Но если ты не писать что-то, что должно работать повсюду, то в тот момент, когда у вас возникает соблазн использовать вообще какой-либо Bashism, вам следует остановиться и вместо этого переписать все это на лучшем языке сценариев. Ваше будущее будет вам благодарно.
(Так когда является целесообразно использовать расширения Bash? Первому заказу: никогда. Ко второму порядку: только для расширения интерактивной среды Bash - например, чтобы обеспечить умное завершение табуляции и необычные подсказки.)