#! / bin / sh vs #! / bin / bash для максимальной переносимости

36

Обычно я работаю с серверами Ubuntu LTS , который от того, что я понимаю симлинк /bin/shк /bin/dash. Много других дистрибутивов, хотя символическая ссылка /bin/shна /bin/bash.

Исходя из этого я понимаю, что если скрипт использует #!/bin/shсверху, он может не работать одинаково на всех серверах?

Существует ли рекомендация, какую оболочку использовать для сценариев, когда требуется максимальная переносимость этих сценариев между серверами?

cherouvim
источник
Есть небольшие различия между различными оболочками. Если для вас важнее всего переносимость, используйте #!/bin/shи не используйте ничего, кроме того, что было в оригинальной оболочке.
Турбьёрн Равн Андерсен

Ответы:

65

Существует примерно четыре уровня переносимости сценариев оболочки (что касается строки shebang):

  1. Наиболее переносимый: используйте #!/bin/shшебанг и используйте только базовый синтаксис оболочки, указанный в стандарте POSIX . Это должно работать практически на любой системе POSIX / unix / linux. (Ну, за исключением Solaris 10 и более ранних версий, у которых была настоящая устаревшая оболочка Bourne, предшествующая POSIX, настолько несовместимая, как /bin/sh.)

  2. Второй наиболее переносимый: используйте линию #!/bin/bash(или #!/usr/bin/env bash) shebang и придерживайтесь функций bash v3. Это будет работать в любой системе, в которой есть bash (в ожидаемом месте).

  3. Третий наиболее переносимый: используйте #!/bin/bash(или #!/usr/bin/env bash) линию Шебанга и используйте функции bash v4. Это не сработает в любой системе, в которой установлен bash v3 (например, macOS, которая использует его по причинам лицензирования).

  4. Наименее переносимый: используйте #!/bin/shshebang и используйте расширения 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), или с какими-либо экранированием (обратной косой чертой) в строке для печати, разные версии будут делать разные вещи. Каждый раз, когда вы хотите напечатать строку без перевода строки после нее или с экранированием в строке, используйте printfвместо этого (и узнайте, как это работает - это сложнее, чем echoесть). psКоманда также беспорядок .

Еще одна общая вещь, которую стоит посмотреть, это недавние расширения / GNUish для синтаксиса опций команды: старый (стандартный) формат команды состоит в том, что за командой следуют опции (с одной чертой, и каждая опция - одна буква), за которой следует командные аргументы. Последние (и часто непереносимые) варианты включают длинные опции (обычно с ними --), позволяющие опциям следовать за аргументами и использующие --для отделения опций от аргументов.

Гордон Дэвиссон
источник
12
Четвертый вариант - просто неправильная идея. Пожалуйста, не используйте его.
Пабук
3
@pabouk Я полностью согласен, поэтому я отредактировал свой ответ, чтобы прояснить ситуацию.
Гордон Дэвиссон
1
Ваше первое утверждение слегка вводит в заблуждение. Стандарт POSIX не определяет ничего о шебанге, когда внешнее сообщение о том, что его использование ведет к неопределенному поведению. Более того, POSIX не указывает, где должна располагаться оболочка posix, а только ее name ( sh), поэтому /bin/shне гарантируется, что это правильный путь. В таком случае наиболее переносимым является отсутствие указания какого-либо shebang или его адаптация к используемой ОС.
Jlliagre
2
Я недавно попал в # 4 со своим сценарием и просто не мог понять, почему он не работает; в конце концов, те же самые команды работали безошибочно и делали то, что я хотел, когда я пробовал их прямо в оболочке. Как только я изменился #!/bin/shна #!/bin/bash, сценарий работал отлично. (На мой взгляд, этот сценарий со временем превратился из сценария, которому действительно нужны только
CVn
2
@ MichaelKjörling (истинная) оболочка Bourne почти никогда не поставляется вместе с дистрибутивами Linux и в любом случае не совместима с POSIX. Стандартная оболочка POSIX была создана на основе kshповедения, а не Bourne. То, что делает большинство дистрибутивов Linux после FHS, это наличие /bin/shсимволической ссылки на оболочку, которую они выбирают для обеспечения совместимости с POSIX, обычно bashили dash.
jlliagre
5

В ./configureсценарии, который готовит язык TXR к построению, я написал следующий пролог для лучшей переносимости. Сценарий самозагрузится, даже если #!/bin/shон не соответствует POSIX старой Bourne Shell. (Я собираю каждую версию на Solaris 10 VM).

#!/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).

Kaz
источник
xХакерство не понадобилось бы, если бы использовалось правильное цитирование, так как ситуации, которые предписывали эту идиому, окружают устаревшие вызовы тестов -aили -oобъединяют несколько тестов.
Чарльз Даффи
@CharlesDuffy Действительно; test x$whatever, что я там выглядит совершая как лук в лаке. Если мы не можем доверять сломанной старой оболочке в цитировании, то последняя ${@+"$@"}попытка бессмысленна.
Каз
2

Все варианты языка оболочки 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 - например , для обеспечения смарта табуляции завершения и орнаментальные подсказок?.) .

zwol
источник
На днях мне пришлось написать сценарий, который сделал что-то вроде find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -. Невозможно заставить его работать правильно без bashisms ( read -d "") и расширений GNU ( find -print0, tar --null). Переопределение этой строки в Perl будет намного длиннее и неуклюже. Это та ситуация, когда использование bashisms и других расширений, не относящихся к POSIX, является правильным решением.
Микау
@michau Возможно, он больше не будет считаться однострочным, в зависимости от того, что происходит в этих эллипсах, но я думаю, что вы не воспринимаете «переписать все на лучшем языке сценариев» так буквально, как я имел в виду. Мой путь выглядит примерно так: perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdобратите внимание, что вам не только не нужны никакие ошибки, вам не нужны никакие расширения GNU tar. (Если важна длина командной строки или вы хотите, чтобы сканирование и tar выполнялись параллельно, тогда вам это нужно tar --null -T.)
zwol
Дело в том, что findв моем сценарии было возвращено более 50 000 имен файлов, поэтому ваш сценарий может достичь предела длины аргумента. Корректная реализация, которая не использует не-POSIX-расширения, должна будет постепенно наращивать вывод tar или, возможно, использовать Archive :: Tar :: Stream в коде Perl. Конечно, это может быть сделано, но в этом случае Bash-скрипт гораздо быстрее написать, и у него меньше шаблонов, и, следовательно, меньше места для ошибок.
Микау
Кстати, я мог бы использовать Perl вместо Bash в быстрой и краткой форме: find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. Но вопросы таковы: 1) Я использую find -print0и в tar --nullлюбом случае, так какой смысл избегать bashism read -d "", который является точно такой же вещью (расширение GNU для обработки нулевого разделителя, которое, в отличие от Perl, может когда-нибудь стать вещью POSIX )? 2) Perl действительно более распространен, чем Bash? Я недавно использовал образы Docker, и у многих из них есть Bash, но нет Perl. Новые сценарии с большей вероятностью будут запускаться там, чем на древних Unices.
Микау