Насколько сложна программа, написанная на чистом Bash? [закрыто]

17

После некоторого очень быстрого исследования кажется, что Bash является языком, полным Тьюринга .

Интересно, почему Bash используется почти исключительно для написания относительно простых скриптов? Поскольку оболочка Bash поставляется с Linux, вы можете запускать сценарии оболочки без какого-либо внешнего интерпретатора или компилятора, как требуется для других популярных компьютерных языков. Это огромное преимущество, которое в некоторых случаях может компенсировать посредственность самого языка.

Итак, есть ли предел сложности таких программ? Чистый Bash используется для написания сложных программ? Можно ли написать, скажем, файловый компрессор / декомпрессор в чистом Bash? Компилятор? Простая видеоигра?

Это так редко используется только потому, что есть только очень ограниченные средства отладки?

Bregalad
источник
2
shСкрипт , configureкоторый используется как часть процесса сборки для очень многого ипа * х пакетов не является «относительно простым».
user4556274
@ user4556274 Это не так, но обычно оно пишется не вручную, а из обширного набора m4макросов.
Кусалананда
2
В Bash есть ассемблер x86 , так что да, Bash иногда используется для написания сложных программ. Почему люди не делают это чаще? Возможно, потому, что переводчик также медленный, дрянной и склонен к «интересным» ошибкам (см. « Shellshock» ) Кроме того, скрипты Bash экспоненциально усложняются в зависимости от размера. Посмотрите на ассемблер выше; Вы можете сказать из источника, если он следует синтаксису AT & T или Intel?
Satō Katsura
configureСценарии также медленны, выполняют целую кучу бесполезной работы и были предметом некоторых забавных сплетен. Конечно, оболочку можно использовать для больших программ, но с другой стороны, люди также сделали компьютеры из игры жизни и Майнкрафта Конвея , а также есть языки программирования, такие как Brainf ** k и Hexagony . Видимо, некоторым людям просто нравится создавать что-то из действительно маленьких и сбивающих с толку атомов. Вы даже можете продавать игры с этой идеей ...
ilkkachu
Так, этот вопрос отвечает или нет? Они откладывают это и говорят, что это не отвечает, но все же я получаю несколько хороших ответов. Было бы неплохо быть последовательным, поскольку я новичок в этой SE, чтобы направить меня к тому, какие вопросы и какие нежелательны в этой SE.
Брегалад

Ответы:

30

кажется, что Bash - тьюрингово-полный язык

Понятие полноты по Тьюрингу полностью отделено от многих других понятий, полезных в языке для программирования в целом : удобство использования, выразительность, понимание, скорость и т. Д.

Если Тьюринг-полнота были все мы требовали, мы бы не имели каких - либо языков программирования на всех , даже не ассемблере . Все программисты просто пишут в машинном коде , так как наши процессоры также полны по Тьюрингу.

почему Bash используется почти исключительно для написания относительно простых скриптов?

Большие, сложные сценарии оболочки - такие как configureсценарии, выводимые GNU Autoconf, - нетипичны по многим причинам:

  1. До относительно недавнего времени вы не могли рассчитывать на наличие 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? Это неприемлемо во многих случаях; значения по умолчанию.

  2. Ни один из языков сценариев оболочки семейства Bourne не поддерживает модули .

    Наиболее близкой к модульной системе в сценарии оболочки является .команда - также известная sourceв более современных вариантах оболочки Bourne - которая дает сбой на нескольких уровнях по отношению к надлежащей модульной системе, самым основным из которых является пространство имен .

    Независимо от языка программирования, человеческое понимание начинает отмечаться, когда любой отдельный файл в более крупной общей программе превышает несколько тысяч строк. Сама причина, по которой мы структурируем большие программы во множество файлов, заключается в том, что мы можем абстрагировать их содержание не более чем в одно или два предложения. Файл A - это синтаксический анализатор командной строки, файл B - сетевой насос ввода-вывода, файл C - это прокладка между библиотекой Z и остальной частью программы и т. Д. Когда единственным способом для объединения множества файлов в одну программу является включение текста Вы устанавливаете ограничение на разумный рост ваших программ.

    Для сравнения это было бы как если бы язык программирования C не имел компоновщика, только #includeоператоры. Такой диалект C-Lite не нуждается в ключевых словах, таких как externили static. Эти функции существуют для обеспечения модульности.

  3. POSIX не определяет способ для выделения переменных в одной функции сценария оболочки, а тем более в файле.

    Это эффективно делает все переменные глобальными , что опять-таки вредит модульности и компоновке.

    Есть решения для этого в оболочках после POSIX - конечно bash, ksh93и zshпо крайней мере - но это только возвращает вас к пункту 1 выше.

    Вы можете увидеть эффект этого в руководствах по стилю при написании макросов GNU Autoconf, где они рекомендуют ставить имена переменных перед именем самого макроса, что приводит к очень длинным именам переменных исключительно для того, чтобы уменьшить вероятность столкновения до приемлемо близкого нуль.

    Даже С лучше на этот счет на милю. Мало того, что большинство программ на C написаны в основном с локальными переменными функций, C также поддерживает область видимости блоков, позволяя нескольким блокам в одной функции повторно использовать имена переменных без перекрестного загрязнения.

  4. Языки программирования оболочки не имеют стандартной библиотеки.

    Можно утверждать, что стандартная библиотека языка сценариев оболочки является содержимым PATH, но это просто говорит о том, что для достижения каких-либо последствий, сценарий оболочки должен вызывать другую целую программу, вероятно, написанную на более мощном языке, чтобы начинать с.

    Также нет широко используемого архива служебных библиотек оболочки, как в CPAN Perl . Без большой доступной библиотеки стороннего служебного кода программист должен писать больше кода вручную, поэтому он менее продуктивен.

    Даже игнорируя тот факт, что большинство сценариев оболочки полагаются на внешние программы, обычно написанные на C, для выполнения чего-либо полезного, есть издержки всех этих pipe()fork()exec()цепочек вызовов. Этот шаблон довольно эффективен в Unix по сравнению с IPC и запуском процессов в других ОС, но здесь он фактически заменяет то, что вы делаете, вызовом подпрограммы на другом языке сценариев, который все еще гораздо эффективнее. Это серьезно ограничивает скорость выполнения сценариев оболочки.

  5. Сценарии оболочки имеют мало встроенных возможностей для повышения производительности за счет параллельного выполнения.

    Bourne снарядов &, waitи трубопроводы для этого, но это в основном используется только для составления нескольких программ, а не для достижения CPU или параллелизм ввода / вывода. Вы вряд ли сможете привязать ядра или насытить массив RAID исключительно с помощью сценариев оболочки, и если вы это сделаете, вы, вероятно, сможете добиться гораздо более высокой производительности на других языках.

    Конвейеры, в частности, являются слабым способом повышения производительности за счет параллельного выполнения. Он позволяет только двум программам работать параллельно, и одна из двух, вероятно, будет заблокирована при вводе-выводе в другую или в любой другой момент времени.

    Есть новоявленные пути вокруг этого, такие , как xargs -Pи GNUparallel , но это просто переходит к пункту 4 выше.

    Поскольку встроенные возможности не позволяют полностью использовать преимущества многопроцессорных систем, сценарии оболочки всегда будут работать медленнее, чем хорошо написанная программа на языке, который может использовать все процессоры в системе. Чтобы configureснова взять этот пример сценария GNU Autoconf , удвоение количества ядер в системе мало что изменит для повышения скорости ее работы.

  6. Языки сценариев оболочки не имеют указателей или ссылок .

    Это мешает вам делать кучу вещей, которые легко сделать на других языках программирования.

    Во-первых, невозможность косвенной ссылки на другую структуру данных в памяти программы означает, что вы ограничены встроенными структурами данных . Ваша оболочка может иметь ассоциативные массивы , но как они реализованы? Есть несколько возможностей, каждая из которых имеет свой компромисс: красно-черные деревья , деревья AVL и хеш-таблицы являются наиболее распространенными, но есть и другие. Если вам нужен другой набор компромиссов, вы застряли, потому что без ссылок у вас не будет возможности вручную прокрутить многие типы сложных структур данных. Вы застряли с тем, что вам дали.

    Или, может быть, вам нужна структура данных, в которой даже нет адекватной альтернативы, встроенной в интерпретатор сценария оболочки, такой как направленный ациклический граф , который может вам понадобиться для моделирования графа зависимостей . Я программировал десятилетиями, и единственный способ, которым я мог придумать, чтобы сделать это в сценарии оболочки, - это злоупотреблять файловой системой , используя символические ссылки в качестве ложных ссылок. Это решение, которое вы получаете, когда полагаетесь только на полноту Тьюринга, которая ничего не говорит о том, является ли решение элегантным, быстрым или простым для понимания.

    Усовершенствованные структуры данных - это всего лишь одно использование указателей и ссылок. Для них есть куча других приложений , которые просто невозможно сделать с помощью языка сценариев оболочки семейства Борнов.

Я мог бы продолжать и продолжать, но я думаю, что вы здесь понимаете. Проще говоря, есть много более мощных языков программирования для систем типа Unix.

Это огромное преимущество, которое в некоторых случаях может компенсировать посредственность самого языка.

Конечно, именно поэтому GNU Autoconf использует целенаправленно ограниченное подмножество языков сценариев оболочки Bourne для своих configureвыходных данных сценариев: так что его configureсценарии будут работать практически везде.

Вы, вероятно, не найдете большую группу сторонников полезности написания на очень переносимом диалекте оболочки Bourne, чем разработчики GNU Autoconf, хотя их собственное творение написано в основном на Perl, плюс некоторые m4, и только немного оболочки сценарий; только вывод Autoconf - это скрипт оболочки Bourne. Если это не вызывает вопроса о том, насколько полезна концепция «Борн везде», я не знаю, что будет.

Итак, есть ли предел сложности таких программ?

Технически говоря, нет, как предполагает ваше наблюдение за полнотой по Тьюрингу.

Но это не то же самое, что сказать, что произвольно большие скрипты оболочки приятно писать, легко отлаживать или быстро выполнять.

Можно ли написать, скажем, файловый компрессор / декомпрессор в чистом bash?

«Чистый» Баш, без каких-либо призывов к вещам в PATH? Компрессор, вероятно, выполним с использованием echoшестнадцатеричных escape-последовательностей, но это было бы довольно болезненно. Распаковщик может быть невозможно записать таким образом из-за невозможности обработки двоичных данных в оболочке . В конечном итоге вы будете вызывать odи тому подобное для перевода двоичных данных в текстовый формат, родной способ обработки данных оболочкой.

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

И в этом все дело. Если вам нужна программа для быстрого запуска или если она должна быть мощной сама по себе, а не заимствовать силы у других, вы не пишете ее в оболочке.

Простая видеоигра?

Вот тетрис в оболочке . Другие такие игры доступны, если вы идете смотреть.

Есть только очень ограниченные средства отладки

Я бы поставил поддержку средств отладки примерно на 20-е место в списке функций, необходимых для поддержки программирования в целом. Многие программисты в большей степени полагаются на printf()отладку, чем на правильные отладчики, независимо от языка.

В оболочке у вас есть echoи set -x, что в совокупности достаточно для отладки множества проблем.

Уоррен Янг
источник
2
«У сценариев оболочки мало встроенных возможностей для параллельного выполнения». На мой взгляд, оболочка имеет лучшую поддержку параллельной обработки, чем большинство других языков. С помощью одного символа &вы можете запускать процессы параллельно. Вы можете waitдля дочерних процессов завершить. Вы можете настроить конвейеры и более сложные сети каналов, используя именованные каналы. Самое главное, что параллельную обработку просто выполнить правильно, с очень небольшим количеством стандартного кода и избежать рисков и трудностей многопоточности с разделяемой памятью.
Сэм Уоткинс
@SamWatkins: Я обновил пункт 5 выше, чтобы ответить на ваш ответ. Хотя я тоже являюсь поклонником передачи сообщений между отдельными процессами, чтобы избежать многих проблем, присущих параллелизму с общей памятью, здесь я хотел сказать о повышении производительности, а не о возможности компоновки и т. Д. часто требует параллелизма с общей памятью.
Уоррен Янг
Сценарии оболочки хороши для создания прототипов, но в конечном итоге проект должен перейти на надлежащий язык программирования, а затем в идеале скомпилированный язык. Затем в крайних случаях сборка, как вы могли бы увидеть в проекте FFmpeg. Cmake - хороший пример того, что должно случиться с Autotools - он написан на C и не требует Perl, Texinfo или M4. Это действительно смущает, что Autotools все еще так сильно полагается на сценарии оболочки после 30 лет wikipedia.org/wiki/GNU_Build_System#Criticism
Стивен Пенни,
9

Мы можем гулять или плавать где угодно, так почему мы беспокоимся о велосипедах, автомобилях, поездах, лодках, самолетах и ​​других транспортных средствах? Конечно, прогулки или плавание могут быть утомительными, но есть огромное преимущество в том, что не нужно никакого дополнительного оборудования.

Во-первых, хотя 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. Некоторые детали варьируются от оболочки к оболочке, но общая идея та же.

Жиль "ТАК - перестань быть злым"
источник
1
Я считаю, что миф о переносимости обсуждается довольно часто, но я не уверен, что этот конкретный элемент я бы использовал как отрицательный, поскольку он применим и к большинству других языков, включая Java. Даже PHP, работающий на Windows-сервере против * NIX-сервера, имеет некоторые небольшие различия, о которых вы всегда должны знать, если вы достаточно глупы, чтобы что-то запускать на Windows-сервере, то есть. Многие вещи не работают на Android или IO, поэтому не уверен, что это может быть правильным комментарием.
Lizardx
7

Некоторые причины не использовать сценарии оболочки для больших программ, просто из головы:

  • Большинство функций выполняются путем отключения внешних команд, что является медленным. В отличие от языков программирования, таких как Perl, можно сделать эквивалентно mkdirили grepвнутренне.
  • Нет простого способа получить доступ к библиотекам C или сделать прямые системные вызовы, что означает, что, например, видеоигру будет сложно создать.
  • Правильные языки программирования лучше поддерживают сложные структуры данных. Хотя у Bash есть массивы и ассоциативные массивы, но я бы не хотел думать о связанном списке или дереве.
  • Оболочка сделана для обработки команд, которые сделаны, если текст. Двоичные данные (то есть переменные, содержащие NUL-байты (байты со значением ноль)) трудно обрабатывать. Немного зависит от оболочки, zshимеет некоторую поддержку. Это также потому, что интерфейс для внешних программ в основном текстовый и \0используется в качестве разделителя.
  • Также из-за внешних команд разделение между кодом и данными немного затруднено. Обратите внимание на все проблемы, возникающие при цитировании данных в другой оболочке (т. Е. При запуске bash -c ...или ssh -c ...)
ilkkachu
источник
Это самый точный набор негативов для меня, как для человека, который делает много больших bash-скриптов, это примерно то, что я бы назвал негативами. Тем не менее, я обнаружил, что Bash на самом деле не намного медленнее, чем другие скомпилированные языки при сравнении схожих функций. У меня есть подлое подозрение, что если я попытаюсь написать некоторые из более сложных вещей, которые у меня есть в bash на python, разница в скорости не сделает чудовищную работу, стоящую когда-либо, того стоящей. Тем не менее, один только Bash я нашел слишком ограниченным, но Bash + gawk работает хорошо, gawk почти реальна.
Lizardx