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


36

Зазвичай я працюю з серверами Ubuntu LTS , який від того, що я розумію симлінк /bin/shдо /bin/dash. Дуже багато інших дистрибутивів, хоча і посилаються /bin/shна /bin/bash.

З цього я розумію, що якщо сценарій використовується #!/bin/shзверху, він може працювати не однаково на всіх серверах?

Чи пропонується практика, яку оболонку використовувати для скриптів, коли хочеться максимальної переносимості цих сценаріїв між серверами?


Між різними оболонками є невеликі відмінності. Якщо портативність є найбільш важливою для вас, тоді використовуйте #!/bin/shі не використовуйте нічого іншого, ніж те, що було надано в оригінальній оболонці.
Thorbjørn Ravn Andersen

Відповіді:


65

Існує приблизно чотири рівні переносимості для скриптів оболонки (що стосується рядка shebang):

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

  2. Другий за величиною портативний: використовуйте #!/bin/bash(або #!/usr/bin/env bash) лінію shebang та дотримуйтесь функцій bash v3. Це буде працювати в будь-якій системі, яка має bash (у очікуваному місці).

  3. Третя найбільш портативна: використовуйте #!/bin/bash(або #!/usr/bin/env bash) лінію shebang та використовуйте функції bash v4. Це не вдасться в будь-якій системі, яка має bash v3 (наприклад, macOS, який повинен використовувати її з ліцензійних причин).

  4. Найменше портативний: використовуйте #!/bin/shшебанг і використовуйте розширення bash для синтаксису оболонки POSIX. Це не вдасться в будь-якій системі, яка має щось інше, ніж bash for / bin / sh (наприклад, останні версії Ubuntu). Ніколи цього не робіть; це не просто питання сумісності, це просто невірно. На жаль, це багато помилок.

Моя рекомендація: використовуйте найконсервативніший з перших трьох, який забезпечує всі функції оболонки, необхідні для сценарію. Для максимальної портативності використовуйте варіант №1, але, на мій досвід, деякі функції bash (наприклад, масиви) є досить корисними, що я перейду з №2.

Найгірше, що ви можете зробити, це №4, використовуючи неправильний шебанг. Якщо ви не впевнені, які функції є базовими POSIX, а які - розширення bash, або дотримуйтесь bash shebang (тобто варіант №2), або ретельно протестуйте сценарій із дуже базовою оболонкою (наприклад, тире на ваших серверах Ubuntu LTS). У вікі Ubuntu є хороший список башизмів, на які слід стежити .

У питаннях Unix та Linux "Що означає бути сумісним?" Є кілька дуже хороших відомостей про історію та відмінності між оболонками. і питання Stackoverflow "Різниця між sh і bash" .

Також пам’ятайте, що оболонка - не єдине, що відрізняється між різними системами; якщо ви звикли до Linux, ви звикли до команд GNU, у яких багато нестандартних розширень, яких ви не можете знайти в інших системах unix (наприклад, bsd, macOS). На жаль, тут немає простого правила, ви просто повинні знати діапазон варіацій для команд, які ви використовуєте.

Один з найбільш гидких команд з точки зору переносимості є одним із самих основних: echo. Кожного разу, коли ви використовуєте його з будь-якими параметрами (наприклад, echo -nабо echo -e), або з будь-якими втечами (зворотними косою рисою) у рядку для друку, різні версії будуть робити різні речі. Кожен раз, коли ви хочете надрукувати рядок без передачі рядка після неї, або зі скачками в рядку, використовуйте printfнатомість (і дізнайтеся, як це працює - це складніше, ніж echoє). psКоманда також безлад .

Ще одна загальна річ, яку слід спостерігати, - це нещодавні розширення GNUish до синтаксису командного параметра: старий (стандартний) формат команди полягає в тому, що команді слідують параметри (з одним тире, і кожна опція - одна літера), а потім аргументи команд. Останні (а часто і не портативні) варіанти включають довгі параметри (зазвичай вводяться разом --), що дозволяють опціям приймати після аргументів і використовувати --для відокремлення параметрів від аргументів.


12
Четвертий варіант - просто неправильна ідея. Будь ласка, не використовуйте його.
pabouk

3
@pabouk Я згоден повністю, тому я відредагував свою відповідь, щоб зробити це зрозумілішим.
Гордон Девіссон

1
Ваше перше твердження злегка вводить в оману. Стандарт POSIX нічого не вказує про те, що вона знаходить зовнішнє використання, призводить до невказаної поведінки. Більше того, POSIX не вказує, де повинна знаходитися оболонка posix, лише її ім'я ( sh), тому /bin/shне гарантується правильний шлях. Найбільш портативний - це взагалі не вказувати будь-який шебанг або не адаптувати шебанг до використовуваної ОС.
jlliagre

2
Я потрапив у №4 із моїм сценарієм зовсім недавно, і просто не міг зрозуміти, чому це не працює; зрештою, ті ж команди працювали непогано, і робили саме те, що я хотів, щоб вони зробили, коли я спробував їх безпосередньо в оболонці. Як тільки я змінив #!/bin/shдо #!/bin/bashхоча, сценарій працював відмінно. (На мій захист, цей сценарій еволюціонував з часом від одного, якому справді потрібні лише ш-ізми, до такого, який спирався на баш-поведінку.)
CVn

2
@ MichaelKjörling (справжня) оболонка Bourne майже ніколи не постачається з дистрибутивами Linux і не є сумісною з POSIX. Стандартна оболонка POSIX була створена з kshповедінки, а не Борна. Більшість дистрибутивів Linux після FHS - /bin/shце символічне посилання на оболонку, яку вони обирають, щоб забезпечити сумісність POSIX, як правило, bashабо dash.
jlliagre

5

У ./configureсценарії, який готує мову TXR до побудови, я написав наступний пролог для кращої портативності. Сценарій завантажиться сам, навіть якщо #!/bin/shце стара, яка не відповідає стандарту POSIX. (Я будую кожен реліз на 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якщо Баш не знайдеться.

Пролог написаний на консервативному діалекті оболонки, використовуючи testдля тестів існування файлів, і ${@+"$@"}хитрість для розширення аргументів, що стосуються деяких зламаних старих оболонок (що було б просто, "$@"якби ми опинилися в оболонці, що відповідає POSIX).


xЗловмисникам не знадобилося б, якщо використовувались правильні цитування, оскільки ситуації, які вимагають, щоб ідіома оточувала застарілі тестові виклики -aабо -oпоєднувала кілька тестів.
Чарльз Даффі

@CharlesDuffy Дійсно; те, test x$whateverщо я там вчиняю, схоже на цибулю в лаку. Якщо ми не можемо довірити розбитій старій оболонці для цитування, то остаточна ${@+"$@"}спроба є безглуздою.
Каз

2

Усі варіанти мови оболонки Борна об'єктивно жахливі порівняно з сучасними мовами сценаріїв, такими як Perl, Python, Ruby, node.js і навіть (можливо) Tcl. Якщо вам доведеться зробити що-небудь навіть трохи складне, ви з часом будете щасливішими, якщо замість сценарію оболонки ви будете використовувати одне з перерахованих вище.

Єдиною перевагою мови оболонки, яка все ще є над новими мовами, є те, що щось, що викликає себе /bin/sh, гарантовано існує в усьому, що вважається Unix. Однак це може бути навіть не сумісним з POSIX; багато зі спадщини патентованого Unixes заморозив мову , реалізований /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 оболонку за цієї обставини, але я був би готовий виправити POSIXism, якщо хтось попросить підтримку іржавого заліза.

Але якщо ви не пишете те, що має працювати всюди, тоді, коли ви спокуситесь використовувати будь-який башизм, вам слід зупинитись і переписати всю справу на кращій мові сценаріїв. Ваше майбутнє «я вам вдячне».

(Тому , коли НЕ асигнувати використовувати розширення Bash Для першого порядку: ніколи Для другого порядку: тільки для розширення інтерактивного середовища Bash - наприклад , для забезпечення смарта табуляції завершення і орнаментальні підказок ?.) .


Днями мені довелося написати сценарій, який зробив щось подібне find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -. Не вдасться змусити його правильно працювати без башизмів ( read -d "") та розширень GNU ( find -print0, tar --null). Повторне виконання цієї лінії в Perl було б набагато довше і незграбніше. Така ситуація, коли використання башизмів та інших не-POSIX розширень - це правильна річ.
michau

@michau Це, можливо, більше не вважатиметься однолінійним, залежно від того, що йде в цих еліпсах, але я думаю, ви не приймаєте "переписати всю річ кращою мовою сценарію" так буквально, як я це мав на увазі. Мій шлях виглядає приблизно так, як perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdзауважте, що не тільки вам не потрібні ніякі башизми, ви також не потребуєте розширення гудрону GNU. (Якщо довжина командного рядка викликає занепокоєння, або ви хочете, щоб сканування та tar запускалися паралельно, тоді вам це потрібно tar --null -T.)
zwol

Справа в тому, що findза моїм сценарієм було повернуто понад 50 000 імен файлів, тому ваш сценарій, ймовірно, досягне межі довжини аргументу. Правильна реалізація, яка не використовує розширення, що не є POSIX, повинна будувати вихід тарінгу поступово або, можливо, використовувати Архів :: Tar :: Stream у коді Perl. Звичайно, це можна зробити, але в цьому випадку сценарій Bash набагато швидше написати і має менше котлів, а отже, менше місця для помилок.
michau

До речі, я міг би використовувати Perl замість Bash в швидкої і короткій формі: find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. Але питання такі: 1) Я використовую find -print0і tar --nullвсе одно, тож у чому сенс уникати башизму read -d "", який є саме тим самим (розширення GNU для обробки нульового сепаратора, яке, на відміну від Perl, може колись стати річчю POSIX )? 2) Чи справді Перл є більш поширеним, ніж Баш? Я нещодавно використовую зображення Docker, і в багатьох з них є Bash, але немає Perl. Нові сценарії частіше запускаються там, ніж у стародавніх уніках.
michau
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.