На работе я часто пишу сценарии bash. Мой руководитель предложил разбить весь сценарий на функции, как показано в следующем примере:
#!/bin/bash
# Configure variables
declare_variables() {
noun=geese
count=three
}
# Announce something
i_am_foo() {
echo "I am foo"
sleep 0.5
echo "hear me roar!"
}
# Tell a joke
walk_into_bar() {
echo "So these ${count} ${noun} walk into a bar..."
}
# Emulate a pendulum clock for a bit
do_baz() {
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
}
# Establish run order
main() {
declare_variables
i_am_foo
walk_into_bar
do_baz
}
main
Есть ли какая-либо причина, чтобы сделать это, кроме «читабельности», которая, я думаю, могла бы быть одинаково хорошо обоснована еще несколькими комментариями и небольшим межстрочным интервалом?
Делает ли это сценарий более эффективным (я бы ожидал обратного, если что-нибудь), или это облегчает изменение кода за пределами вышеупомянутого потенциала читабельности? Или это действительно просто стилистическое предпочтение?
Обратите внимание, что, хотя сценарий демонстрирует это не очень хорошо, «порядок выполнения» функций в наших реальных сценариях имеет тенденцию быть очень линейным - walk_into_bar
зависит от того, что i_am_foo
сделано, и do_baz
действует от материала, установленного walk_into_bar
- так что возможность произвольно поменять порядок выполнения - это не то, что мы обычно делаем. Например, вы бы не захотели вдруг поставить declare_variables
после walk_into_bar
, что сломало бы вещи.
Пример того, как я написал бы вышеупомянутый сценарий, был бы:
#!/bin/bash
# Configure variables
noun=geese
count=three
# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"
# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."
# Emulate a pendulum clock for a bit
for i in {1..6}; do
expr $i % 2 >/dev/null && echo "tick" || echo "tock"
sleep 1
done
источник
main()
вверху и добавляюmain "$@"
внизу, чтобы вызвать его. Это позволяет вам сразу увидеть высокоуровневую логику скрипта при ее открытии.local
- это обеспечивает переменную область действия, которая невероятно важна в любом нетривиальном скрипте.Ответы:
Я начал использовать тот же стиль программирования bash после прочтения поста Kfir Lavi в блоге «Defensive Bash Programming» . Он приводит немало веских причин, но лично я считаю их наиболее важными:
процедуры становятся описательными: гораздо легче понять, что должна делать конкретная часть кода. Вместо стены кода вы видите «О,
find_log_errors
функция читает этот файл журнала на наличие ошибок». Сравните это с нахождением целого ряда строк awk / grep / sed, которые используют бог знает, какой тип регулярных выражений в середине длинного скрипта - вы не представляете, что там происходит, если нет комментариев.Вы можете отлаживать функции, заключая в
set -x
иset +x
. Как только вы узнаете, что остальная часть кода работает нормально, вы можете использовать этот трюк, чтобы сосредоточиться на отладке только этой конкретной функции. Конечно, вы можете заключить части скрипта, но что, если это длинная часть? Проще сделать что-то вроде этого:использование печати с
cat <<- EOF . . . EOF
. Я использовал его несколько раз, чтобы сделать свой код более профессиональным. Кроме того,parse_args()
сgetopts
функцией довольно удобно. Опять же, это помогает с удобочитаемостью, вместо того, чтобы помещать все в сценарий как гигантскую стену текста. Это также удобно использовать повторно.И, очевидно, это гораздо более читабельно для тех, кто знает C, Java или Vala, но имеет ограниченный опыт работы с bash. Что касается эффективности, то вы мало что можете сделать - сам bash не самый эффективный язык, и люди предпочитают perl и python, когда речь идет о скорости и эффективности. Тем не менее, вы можете
nice
функцию:По сравнению с вызовом nice для каждой строки кода, это уменьшает количество набираемых AND и может быть удобно использовано, когда вы хотите, чтобы только часть вашего скрипта выполнялась с более низким приоритетом.
Запуск функций в фоновом режиме, на мой взгляд, также помогает, если вы хотите, чтобы целый набор операторов выполнялся в фоновом режиме.
Некоторые из примеров, где я использовал этот стиль:
источник
local
и вызывая все черезmain()
функцию. Это делает вещи намного более управляемыми, и вы можете избежать потенциально грязной ситуации.Читаемость это одно. Но есть нечто большее, чем модульность . ( Полумодуляризация может быть более правильной для функций.)
В функциях вы можете хранить некоторые переменные локально, что повышает надежность , уменьшая вероятность того, что все испортится.
Другим преимуществом функций является возможность повторного использования . После того, как функция закодирована, ее можно применять несколько раз в сценарии. Вы также можете перенести его на другой скрипт.
Ваш код сейчас может быть линейным, но в будущем вы можете войти в сферу многопоточности или многопроцессорности в мире Bash. Как только вы научитесь выполнять функции в функциях, вы будете хорошо подготовлены для того, чтобы шагнуть в параллель.
Еще один момент, чтобы добавить. Как отмечает Etsitpab Nioliv в приведенном ниже комментарии, легко перенаправить из функций в единое целое. Но есть еще один аспект перенаправлений с функциями. А именно, перенаправления могут быть установлены вдоль определения функции. Например.:
Теперь для вызова функций не требуется явного перенаправления.
Это может сэкономить много повторений, что снова повышает надежность и помогает поддерживать порядок.
Смотрите также
источник
source
или. scriptname.sh
, и использовать эти функции, как если бы они были в вашем новом скрипте.В своем комментарии я упомянул три преимущества функций:
Их легче проверить и проверить правильность.
Функции могут быть легко использованы в следующих сценариях
Твой босс любит их.
И никогда не стоит недооценивать важность номера 3.
Я хотел бы затронуть еще одну проблему:
Чтобы получить преимущество от разбиения кода на функции, нужно постараться сделать функции максимально независимыми. Если
walk_into_bar
требуется переменная, которая не используется в другом месте, то эта переменная должна быть определена в и сделана локальной дляwalk_into_bar
. Процесс разделения кода на функции и минимизации их взаимозависимостей должен сделать код более понятным и простым.В идеале функции должны легко тестироваться индивидуально. Если из-за взаимодействий их нелегко протестировать, это признак того, что им может быть полезен рефакторинг.
источник
;-)
Вы разбиваете код на функции по той же причине, что и для C / C ++, python, perl, ruby или любого другого кода языка программирования. Более глубокая причина - абстракция - вы инкапсулируете задачи более низкого уровня в примитивы (функции) более высокого уровня, так что вам не нужно беспокоиться о том, как все делается. В то же время код становится более читабельным (и обслуживаемым), а логика программы становится более понятной.
Однако, глядя на ваш код, я нахожу довольно странным иметь функцию для объявления переменных; это действительно заставляет меня поднять брови.
источник
main
функции / методе, тогда?Хотя я полностью согласен с возможностью повторного использования , удобочитаемостью и деликатным целованием боссов, но есть еще одно преимущество функций в bash : переменная область видимости . Как показывает ЛДП :
Я не вижу этого очень часто в сценариях оболочки реального мира, но это кажется хорошей идеей для более сложных сценариев. Уменьшение сплоченности помогает избежать ошибок, когда вы забиваете переменную, ожидаемую в другой части кода.
Повторное использование часто означает создание общей библиотеки функций и
source
внедрение этой библиотеки во все ваши сценарии. Это не поможет им работать быстрее, но поможет вам писать их быстрее.источник
local
, но я думаю, что большинство людей, пишущих сценарии, разбитые на функции, все еще следуют принципу разработки. Usignlocal
только усложняет введение ошибок.local
делает переменные доступными для функции и ее потомков, поэтому очень приятно иметь переменную, которая может передаваться из функции A, но недоступна для функции B, которая может хотеть иметь переменную с тем же именем, но с другим назначением. Так что это хорошо для определения объема, и, как сказал Во, - меньше ошибокСовершенно иная причина, нежели те, которые уже приведены в других ответах: одна из причин, по которой этот метод иногда используется, когда единственным не-определением оператора на верхнем уровне является вызов
main
, состоит в том, чтобы убедиться, что скрипт случайно не делает ничего неприятного если скрипт урезан. Сценарий может быть усечен, если он передается из процесса A в процесс B (оболочку), и процесс A завершается по любой причине до того, как он завершил написание всего сценария. Это особенно вероятно, если процесс A извлекает сценарий из удаленного ресурса. Хотя по соображениям безопасности это не очень хорошая идея, это то, что делается, и некоторые сценарии были изменены, чтобы предвидеть проблему.источник
main()
шаблон является обычным в Python, где каждый используетif __name__ == '__main__': main()
в конце файла.import
использовать текущий сценарий без запускаmain
. Я полагаю, что подобный сторож может быть добавлен в скрипт bashПроцесс требует последовательности. Большинство задач являются последовательными. Нет смысла связываться с заказом.
Но самое главное в программировании, которое включает в себя сценарии, это тестирование. Тестирование, тестирование, тестирование. Какие тестовые сценарии у вас сейчас есть для проверки правильности ваших сценариев?
Ваш босс пытается помочь вам стать сценаристом и стать программистом. Это хорошее направление, чтобы войти. Люди, которые придут после вас, будут вам нравиться.
НО. Всегда помните свои ориентированные на процесс корни. Если имеет смысл упорядочить функции в той последовательности, в которой они обычно выполняются, сделайте это, по крайней мере, в качестве первого прохода.
Позже вы увидите, что некоторые из ваших функций обрабатывают ввод, другие выводят, другие обрабатывают, другие моделируют данные и другие манипулируют данными, поэтому может быть целесообразно группировать подобные методы, возможно даже перемещая их в отдельные файлы. ,
Позже вы, возможно, поймете, что вы сейчас написали библиотеки небольших вспомогательных функций, которые вы используете во многих своих скриптах.
источник
Как я продемонстрирую, комментарии и интервалы не могут приблизиться к удобочитаемости функций. Без функций вы не можете видеть лес за деревьями - большие проблемы скрываются среди множества деталей. Другими словами, люди не могут одновременно сосредоточиться на мелких деталях и на общей картине. Это может быть неочевидно в коротком сценарии; пока оно остается коротким, оно может быть достаточно читабельным. Программное обеспечение становится больше, но не меньше, и, безусловно, оно является частью всей системы программного обеспечения вашей компании, которая, безусловно, намного больше, вероятно, миллионы строк.
Подумайте, дал ли я вам такие инструкции:
К тому времени, когда вы пройдете половину, или даже 5%, вы уже забыли, каковы были первые несколько шагов. Вы не могли бы определить большинство проблем, потому что вы не могли видеть лес за деревьями. Сравните с функциями:
Это, безусловно, гораздо более понятно, независимо от того, сколько комментариев вы можете поместить в построчную последовательную версию. Это также делает его гораздо более вероятно , вы заметите , что вы забыли сделать кофе, и , вероятно , забыли sit_down () в конце. Когда ваш разум размышляет о деталях регулярных выражений grep и awk, вы не можете придумать общую картину - «что делать, если не будет кофе»?
Функции в первую очередь позволяют вам увидеть общую картину и заметить, что вы забыли приготовить кофе (или что кто-то может предпочесть чай). В другое время, в другом настроении, вы беспокоитесь о детальной реализации.
Конечно, есть и другие преимущества, которые обсуждаются в других ответах. Другое преимущество, которое не указано в других ответах, состоит в том, что функции обеспечивают гарантию, которая важна для предотвращения и исправления ошибок. Если вы обнаружите, что какая-то переменная $ foo в правильной функции walk_to () была неправильной, вы знаете, что вам нужно только взглянуть на остальные 6 строк этой функции, чтобы найти все, на что могла повлиять эта проблема, и все, что могло вызвало это быть неправильно. Без (правильных) функций, все и вся во всей системе может быть причиной некорректности $ foo, а $ foo может повлиять на все и вся. Поэтому вы не можете безопасно исправить $ foo без повторного изучения каждой строки программы. Если $ foo локально для функции,
источник
bash
синтаксис. Это позор, хотя; Я не думаю, что есть способ передать ввод таким функциям. (т.е.pour();
<coffee
). Это больше похоже наc++
илиphp
(я думаю).Некоторые актуальные истины о программировании:
Комментарии начинаются как временная преграда для неспособности четко выразить свои идеи в коде *, и ухудшаются (или просто ошибаются) с изменением. Поэтому, если это вообще возможно, выражайте понятия, структуры, рассуждения, семантику, поток, обработку ошибок и все остальное, имеющее отношение к пониманию кода как кода.
Тем не менее, у функций Bash есть некоторые проблемы, которых нет в большинстве языков:
local
ключевое слово, это приведет к загрязнению глобального пространства имен.local foo="$(bar)"
результатов в потере кода выходаbar
."$@"
означает в разных контекстах.* Прошу прощения, если это оскорбляет, но после использования комментариев в течение нескольких лет и разработки без них ** в течение нескольких лет становится ясно, что лучше.
** Использование комментариев для лицензирования, документации по API и т. П. Все еще необходимо.
источник
local foo=""
Тогда установив их с помощью выполнения команд , чтобы действовать на результат ...foo="$(bar)" || { echo "bar() failed"; return 1; }
. Это быстро выводит нас из функции, когда требуемое значение не может быть установлено. Фигурные скобки необходимы, чтобы гарантировать, чтоreturn 1
выполняется только при неудаче.Время - деньги
Есть и другие хорошие ответы, которые проливают свет на технические причины написания модульных сценариев, потенциально длинных, разработанных в рабочей среде, разработанных для использования группой людей, а не только для вашего собственного использования.
Я хочу сосредоточиться на одном ожидании: в рабочей среде «время - деньги» . Таким образом, отсутствие ошибок и производительность вашего кода оцениваются вместе с читабельностью , тестируемостью, ремонтопригодностью, рефакторируемостью, возможностью повторного использования ...
Написание кода в «модулях» уменьшит время чтения, необходимое не только самому кодировщику , но даже время, используемое тестерами или боссом. Кроме того, обратите внимание, что время начальника обычно оплачивается больше, чем время программиста, и что ваш начальник оценит качество вашей работы.
Более того, написание в независимых «модулях» кода (даже сценария bash) позволит вам работать «параллельно» с другим компонентом вашей команды, сокращая общее время производства и используя в лучшем случае опыт одного сингла, чтобы просматривать или переписывать часть с никаких побочных эффектов для других, чтобы переработать код, который вы только что написали "как есть"для другой программы / скрипта, для создания библиотек (или библиотек фрагментов), для уменьшения общего размера и связанной с этим вероятности ошибок, для отладки и тщательного тестирования каждой отдельной части ... и, конечно, она организует в логическом разделе вашу программу / скрипт и повысить его читабельность. Все вещи, которые сэкономят время и так деньги. Недостатком является то, что вы должны придерживаться стандартов и комментировать свои функции (что, тем не менее, вы должны делать в рабочей среде).
Соблюдение стандарта вначале замедлит вашу работу, но впоследствии ускорит работу всех остальных (и вас тоже). На самом деле, когда количество участников увеличивается, это становится неизбежной потребностью. Так, например, даже если я считаю, что глобальные переменные должны быть определены глобально, а не в функции, я могу понять стандарт, который инициализирует их в функции с именем
declare_variables()
всегда вызывается в первой строкеmain()
одной ...И последнее, но не менее важное: не стоит недооценивать возможность в современных редакторах исходного кода показывать или скрывать выборочно отдельные подпрограммы ( свертывание кода ). Это позволит сохранить компактность кода и сосредоточить внимание пользователя на экономии времени.
Здесь выше вы можете увидеть, как разворачивается только
walk_into_bar()
функция. Даже другие были длиной в 1000 строк каждая, вы все равно могли держать под контролем весь код на одной странице. Обратите внимание, что он свернут даже в том разделе, куда вы идете, чтобы объявить / инициализировать переменные.источник
Помимо причин, приведенных в других ответах:
источник
Другой причиной, которая часто упускается из виду, является синтаксический анализ bash:
Этот скрипт, очевидно, содержит синтаксическую ошибку, и bash вообще не должен его запускать, верно? Неправильно.
Если мы обернем код в функцию, этого не произойдет:
источник