После некоторого очень быстрого исследования кажется, что Bash является языком, полным Тьюринга .
Интересно, почему Bash используется почти исключительно для написания относительно простых скриптов? Поскольку оболочка Bash поставляется с Linux, вы можете запускать сценарии оболочки без какого-либо внешнего интерпретатора или компилятора, как требуется для других популярных компьютерных языков. Это огромное преимущество, которое в некоторых случаях может компенсировать посредственность самого языка.
Итак, есть ли предел сложности таких программ? Чистый Bash используется для написания сложных программ? Можно ли написать, скажем, файловый компрессор / декомпрессор в чистом Bash? Компилятор? Простая видеоигра?
Это так редко используется только потому, что есть только очень ограниченные средства отладки?
источник
sh
Скрипт ,configure
который используется как часть процесса сборки для очень многого ипа * х пакетов не является «относительно простым».m4
макросов.configure
Сценарии также медленны, выполняют целую кучу бесполезной работы и были предметом некоторых забавных сплетен. Конечно, оболочку можно использовать для больших программ, но с другой стороны, люди также сделали компьютеры из игры жизни и Майнкрафта Конвея , а также есть языки программирования, такие как Brainf ** k и Hexagony . Видимо, некоторым людям просто нравится создавать что-то из действительно маленьких и сбивающих с толку атомов. Вы даже можете продавать игры с этой идеей ...Ответы:
Понятие полноты по Тьюрингу полностью отделено от многих других понятий, полезных в языке для программирования в целом : удобство использования, выразительность, понимание, скорость и т. Д.
Если Тьюринг-полнота были все мы требовали, мы бы не имели каких - либо языков программирования на всех , даже не ассемблере . Все программисты просто пишут в машинном коде , так как наши процессоры также полны по Тьюрингу.
Большие, сложные сценарии оболочки - такие как
configure
сценарии, выводимые GNU Autoconf, - нетипичны по многим причинам:До относительно недавнего времени вы не могли рассчитывать на наличие POSIX-совместимой оболочки везде .
Многие системы, особенно старые, технически имеют POSIX-совместимую оболочку где-то в системе, но она может не находиться в таком предсказуемом месте, как
/bin/sh
. Если вы пишете сценарий оболочки, и он должен работать на разных системах, как тогда вы пишете строку shebang ? Один из вариантов - пойти дальше и использовать/bin/sh
, но выбрать ограничиться диалектом пред-POSIX Bourne, если он запускается в такой системе.Оболочки Борна до POSIX даже не имеют встроенной арифметики; Вы должны позвать
expr
илиbc
сделать это.Даже с оболочкой POSIX вы упускаете ассоциативные массивы и другие функции, которые мы ожидали найти в языках сценариев Unix с тех пор, как Perl впервые стал популярен в начале 1990-х годов .
Этот исторический факт означает, что существует давняя традиция игнорировать многие из мощных функций современных интерпретаторов сценариев оболочки семейства Борнов просто потому, что вы не можете рассчитывать на их повсеместное использование.
Фактически это продолжается и по сей день: Bash не получал ассоциативные массивы до версии 4 , но вы можете быть удивлены тем, сколько систем, которые все еще используются, основаны на Bash 3. Apple все еще поставляет Bash 3 с macOS в 2017 году - очевидно, для причины лицензирования - и серверы Unix / Linux часто работают практически без изменений в течение очень долгого времени, так что у вас может быть стабильная старая система с Bash 3, например, с CentOS 5. Если у вас есть такие системы в вашей среде, вы не можете использовать ассоциативные массивы в сценариях оболочки, которые должны работать на них.
Если ваш ответ на эту проблему в том , что вы только писать скрипты для «современных» систем, то вы должны справиться с тем , что последней общей опорной точкой для большинства Unix оболочка является стандартом POSIX оболочки , которая в значительной степени неизменный , поскольку он был введено в 1989 году. Существует много различных оболочек, основанных на этом стандарте, но все они в той или иной степени отличаются от этого стандарта. Для того, чтобы ассоциативные массивы снова
bash
,zsh
иksh93
все они имеют эту функцию, но есть несколько несовместимостей реализации. Таким образом, ваш выбор - использовать только Bash, или только Zsh, или только использоватьksh93
.Если ваш ответ на эту проблему таков: «просто установите Bash 4»
ksh93
или что-то еще, то почему бы не «просто» установить вместо этого Perl, Python или Ruby? Это неприемлемо во многих случаях; значения по умолчанию.Ни один из языков сценариев оболочки семейства Bourne не поддерживает модули .
Наиболее близкой к модульной системе в сценарии оболочки является
.
команда - также известнаяsource
в более современных вариантах оболочки Bourne - которая дает сбой на нескольких уровнях по отношению к надлежащей модульной системе, самым основным из которых является пространство имен .Независимо от языка программирования, человеческое понимание начинает отмечаться, когда любой отдельный файл в более крупной общей программе превышает несколько тысяч строк. Сама причина, по которой мы структурируем большие программы во множество файлов, заключается в том, что мы можем абстрагировать их содержание не более чем в одно или два предложения. Файл A - это синтаксический анализатор командной строки, файл B - сетевой насос ввода-вывода, файл C - это прокладка между библиотекой Z и остальной частью программы и т. Д. Когда единственным способом для объединения множества файлов в одну программу является включение текста Вы устанавливаете ограничение на разумный рост ваших программ.
Для сравнения это было бы как если бы язык программирования C не имел компоновщика, только
#include
операторы. Такой диалект C-Lite не нуждается в ключевых словах, таких какextern
илиstatic
. Эти функции существуют для обеспечения модульности.POSIX не определяет способ для выделения переменных в одной функции сценария оболочки, а тем более в файле.
Это эффективно делает все переменные глобальными , что опять-таки вредит модульности и компоновке.
Есть решения для этого в оболочках после POSIX - конечно
bash
,ksh93
иzsh
по крайней мере - но это только возвращает вас к пункту 1 выше.Вы можете увидеть эффект этого в руководствах по стилю при написании макросов GNU Autoconf, где они рекомендуют ставить имена переменных перед именем самого макроса, что приводит к очень длинным именам переменных исключительно для того, чтобы уменьшить вероятность столкновения до приемлемо близкого нуль.
Даже С лучше на этот счет на милю. Мало того, что большинство программ на C написаны в основном с локальными переменными функций, C также поддерживает область видимости блоков, позволяя нескольким блокам в одной функции повторно использовать имена переменных без перекрестного загрязнения.
Языки программирования оболочки не имеют стандартной библиотеки.
Можно утверждать, что стандартная библиотека языка сценариев оболочки является содержимым
PATH
, но это просто говорит о том, что для достижения каких-либо последствий, сценарий оболочки должен вызывать другую целую программу, вероятно, написанную на более мощном языке, чтобы начинать с.Также нет широко используемого архива служебных библиотек оболочки, как в CPAN Perl . Без большой доступной библиотеки стороннего служебного кода программист должен писать больше кода вручную, поэтому он менее продуктивен.
Даже игнорируя тот факт, что большинство сценариев оболочки полагаются на внешние программы, обычно написанные на C, для выполнения чего-либо полезного, есть издержки всех этих
pipe()
→fork()
→exec()
цепочек вызовов. Этот шаблон довольно эффективен в Unix по сравнению с IPC и запуском процессов в других ОС, но здесь он фактически заменяет то, что вы делаете, вызовом подпрограммы на другом языке сценариев, который все еще гораздо эффективнее. Это серьезно ограничивает скорость выполнения сценариев оболочки.Сценарии оболочки имеют мало встроенных возможностей для повышения производительности за счет параллельного выполнения.
Bourne снарядов
&
,wait
и трубопроводы для этого, но это в основном используется только для составления нескольких программ, а не для достижения CPU или параллелизм ввода / вывода. Вы вряд ли сможете привязать ядра или насытить массив RAID исключительно с помощью сценариев оболочки, и если вы это сделаете, вы, вероятно, сможете добиться гораздо более высокой производительности на других языках.Конвейеры, в частности, являются слабым способом повышения производительности за счет параллельного выполнения. Он позволяет только двум программам работать параллельно, и одна из двух, вероятно, будет заблокирована при вводе-выводе в другую или в любой другой момент времени.
Есть новоявленные пути вокруг этого, такие , как
xargs -P
и GNUparallel
, но это просто переходит к пункту 4 выше.Поскольку встроенные возможности не позволяют полностью использовать преимущества многопроцессорных систем, сценарии оболочки всегда будут работать медленнее, чем хорошо написанная программа на языке, который может использовать все процессоры в системе. Чтобы
configure
снова взять этот пример сценария GNU Autoconf , удвоение количества ядер в системе мало что изменит для повышения скорости ее работы.Языки сценариев оболочки не имеют указателей или ссылок .
Это мешает вам делать кучу вещей, которые легко сделать на других языках программирования.
Во-первых, невозможность косвенной ссылки на другую структуру данных в памяти программы означает, что вы ограничены встроенными структурами данных . Ваша оболочка может иметь ассоциативные массивы , но как они реализованы? Есть несколько возможностей, каждая из которых имеет свой компромисс: красно-черные деревья , деревья AVL и хеш-таблицы являются наиболее распространенными, но есть и другие. Если вам нужен другой набор компромиссов, вы застряли, потому что без ссылок у вас не будет возможности вручную прокрутить многие типы сложных структур данных. Вы застряли с тем, что вам дали.
Или, может быть, вам нужна структура данных, в которой даже нет адекватной альтернативы, встроенной в интерпретатор сценария оболочки, такой как направленный ациклический граф , который может вам понадобиться для моделирования графа зависимостей . Я программировал десятилетиями, и единственный способ, которым я мог придумать, чтобы сделать это в сценарии оболочки, - это злоупотреблять файловой системой , используя символические ссылки в качестве ложных ссылок. Это решение, которое вы получаете, когда полагаетесь только на полноту Тьюринга, которая ничего не говорит о том, является ли решение элегантным, быстрым или простым для понимания.
Усовершенствованные структуры данных - это всего лишь одно использование указателей и ссылок. Для них есть куча других приложений , которые просто невозможно сделать с помощью языка сценариев оболочки семейства Борнов.
Я мог бы продолжать и продолжать, но я думаю, что вы здесь понимаете. Проще говоря, есть много более мощных языков программирования для систем типа Unix.
Конечно, именно поэтому GNU Autoconf использует целенаправленно ограниченное подмножество языков сценариев оболочки Bourne для своих
configure
выходных данных сценариев: так что егоconfigure
сценарии будут работать практически везде.Вы, вероятно, не найдете большую группу сторонников полезности написания на очень переносимом диалекте оболочки Bourne, чем разработчики GNU Autoconf, хотя их собственное творение написано в основном на Perl, плюс некоторые
m4
, и только немного оболочки сценарий; только вывод Autoconf - это скрипт оболочки Bourne. Если это не вызывает вопроса о том, насколько полезна концепция «Борн везде», я не знаю, что будет.Технически говоря, нет, как предполагает ваше наблюдение за полнотой по Тьюрингу.
Но это не то же самое, что сказать, что произвольно большие скрипты оболочки приятно писать, легко отлаживать или быстро выполнять.
«Чистый» Баш, без каких-либо призывов к вещам в
PATH
? Компрессор, вероятно, выполним с использованиемecho
шестнадцатеричных escape-последовательностей, но это было бы довольно болезненно. Распаковщик может быть невозможно записать таким образом из-за невозможности обработки двоичных данных в оболочке . В конечном итоге вы будете вызыватьod
и тому подобное для перевода двоичных данных в текстовый формат, родной способ обработки данных оболочкой.Как только вы начинаете говорить об использовании сценариев оболочки так, как это было задумано, в качестве клея для управления другими программами
PATH
, двери открываются, потому что теперь вы ограничены только тем, что можно сделать на других языках программирования, то есть вы не имеет ограничений вообще. Скрипт оболочки , который получает весь его силу, вызвав к другим программам вPATH
не работает так быстро , как монолитные программы , написанных на более мощных языках, но это действительно работать.И в этом все дело. Если вам нужна программа для быстрого запуска или если она должна быть мощной сама по себе, а не заимствовать силы у других, вы не пишете ее в оболочке.
Вот тетрис в оболочке . Другие такие игры доступны, если вы идете смотреть.
Я бы поставил поддержку средств отладки примерно на 20-е место в списке функций, необходимых для поддержки программирования в целом. Многие программисты в большей степени полагаются на
printf()
отладку, чем на правильные отладчики, независимо от языка.В оболочке у вас есть
echo
иset -x
, что в совокупности достаточно для отладки множества проблем.источник
&
вы можете запускать процессы параллельно. Вы можетеwait
для дочерних процессов завершить. Вы можете настроить конвейеры и более сложные сети каналов, используя именованные каналы. Самое главное, что параллельную обработку просто выполнить правильно, с очень небольшим количеством стандартного кода и избежать рисков и трудностей многопоточности с разделяемой памятью.Мы можем гулять или плавать где угодно, так почему мы беспокоимся о велосипедах, автомобилях, поездах, лодках, самолетах и других транспортных средствах? Конечно, прогулки или плавание могут быть утомительными, но есть огромное преимущество в том, что не нужно никакого дополнительного оборудования.
Во-первых, хотя bash является завершением по Тьюрингу, он не очень хорошо манипулирует данными, отличными от целых (не слишком больших), строк, (одномерных) массивов строк и конечных отображений из строк в строки. Любой другой тип данных нуждается в обременительном кодировании, что затрудняет написание программы и часто приводит к снижению производительности, что на практике недостаточно. Например, операции с плавающей точкой в bash являются сложными и медленными.
Кроме того, у bash очень мало способов взаимодействия с окружающей средой. Он может запускать процессы, он может выполнять некоторые простые виды доступа к файлам (через перенаправление), и это все. Bash также имеет клиентский сетевой клиент. Bash может выдавать нулевые байты достаточно легко (
printf \\0
), но не может анализировать нулевые байты на своем входе, что делает его непригодным для чтения двоичных данных. Bash не может напрямую делать другие вещи: для этого он должен вызывать внешние программы. И это нормально: оболочки предназначены для запуска внешних программ! Оболочки являются связующим языком для объединения программ. Но если вы используете внешнюю программу, это означает, что она должна быть доступна, и тогда вы уменьшите преимущество переносимости:).Bash не имеет каких-либо функций, которые облегчают написание надежных программ, кроме
set -e
. Он не имеет (полезных) типов, пространств имен, модулей или вложенных структур данных. Ошибки являются проблемой номер один в программировании; в то время как простота написания безошибочных программ не всегда является решающим фактором при выборе языка, bash занимает плохие позиции по этому показателю. Bash также плохо оценивает производительность, когда делает что-то кроме объединения программ.Долгое время bash не работал в Windows, и даже сегодня он не присутствует в установке Windows по умолчанию и не работает полностью изначально (даже в WSL) в том смысле, что не имеет интерфейсов для Родные функции Windows. Bash не работает на iOS и не устанавливается по умолчанию на Android. Таким образом, если вы не пишете приложение только для Unix, bash вовсе не переносим.
Требование компилятора не является проблемой для переносимости. Компилятор работает на машине разработчиков. Требование интерпретатора или сторонних библиотек может быть проблемой, но в Linux это решаемая проблема с помощью дистрибутивных пакетов, а под Windows, Android и iOS люди обычно связывают сторонние компоненты в своем пакете приложения. Таким образом, проблемы переносимости, которые вы имеете в виду, не являются практическими проблемами для обычных приложений.
Мой ответ относится к другим оболочкам, кроме bash. Некоторые детали варьируются от оболочки к оболочке, но общая идея та же.
источник
Некоторые причины не использовать сценарии оболочки для больших программ, просто из головы:
mkdir
илиgrep
внутренне.zsh
имеет некоторую поддержку. Это также потому, что интерфейс для внешних программ в основном текстовый и\0
используется в качестве разделителя.bash -c ...
илиssh -c ...
)источник