Pull to refresh

Comments 67

Кстати, [ это утилита из coreutils ( см. which [ и man test), а [[ это конструкция самого интерпретатора bash.

$ [ a < b ]
равносильно
$ test a < b
отсюда и ошибка b: No such file or directory
Оказался неправ. Мне на почту написали:
Не совсем так, а чаще всего не так:
ubuntu@stage:~$ type [
[ is a shell builtin
ubuntu@stage:~$ type [[
[[ is a shell keyword
ubuntu@stage:~$ type test
test is a shell builtin
ubuntu@stage:~$ which test
/usr/bin/test
ubuntu@stage:~$ which [
/usr/bin/[

Чаще всего при вызове [ ] вызывается shell builtin
Ключевое отличие: shell builtin (который от внешней команды отличается разве что скоростью) vs. shell keyword (который значительно меняет синтаксис и семантику).
set -o errexit не работает внутри функций, поэтому по-осторожнее с заворачиванием всего и вся в функции при использовании этого.

Из перечисленного очень много башизмов, которые будут работать только с #!/bin/bash в шебанге, но не будут с #!/bin/sh.

Очень длинные скрипты нужно разбивать не просто на функции, а на несколько маленьких скриптов — так их проще тестировать.
Но скрипты длиннее нескольких сотен строк лучше не писать на шелле, согласен.

В остальном интересно, есть кое-что новое. Спасибо!
Ну и set -o nounset и set -o errexit не работает на некоторых юниксах. Вместо этого можно использовать set -u и set -e.
Упс, не заметил, что перевод. Тем не менее комментарий выше актуален ;)
Учитывая как много людей думают что bash == sh и оный есть на любой системе, статью неплохо было бы начать с ремарки о том что большая часть указанных возможностей являются bash-специфичными расширениями над POSIX shell, в народе называется башизмами и избегается в кросс-платформенных скриптах. Самые часто встречающиеся башизмы — собственно #!/bin/bash вместо #!/bin/sh и, пожалуй, [[ foo == bar ]] вместо [ foo = bar ].

Что же до собственно статьи, в первой секции обязательно нужно добавить про set -o pipefail и про особенности генерации статусов завершения конвееров. Если в двух словах

$ (false | true) && echo true || echo false
true
$ set -o pipefail
$ (false | true) && echo true || echo false
false
плюсую. и bash-only скрипты лучше именовать .bash, а не .sh
ИМХО все скрипты лучше именовать name (без разширения). В этом случае вы можете переписать скрипт с баша на перл, а перла на руби, а с руби на питон и никому ничего не говорить (оставив совместимость входных параметров и формата вывода), а пользователи ничего не заметят.
А в случае с расширением вы этого как будто сделать не сможете ;)
сможет, но тогда имя файла будет «врать» о своём содержимом.
Случай вообще довольно вычурный. Кстати не сказано что bash-евские скрипты удобно разбивать на отдельные файлы и подключать друг к другу — bash это позволяет. На моей практике они как внешний исполняемый файл и не вызываются друг из друга поэтому и переписывать на другом языке врятли получится.
что это вдруг «не вызываются»?

Пример
borz@debian:~/t$ ls -lFa
-rw-r--r-- 1 borz borz 73 Apr 29 15:58 ext.bash
-rw-r--r-- 1 borz borz 69 Apr 29 15:58 test.bash

borz@debian:~/t$ cat ext.bash
test_fn(){
echo «run test_fn function!»
}

echo «run exclude bash file»

borz@debian:~/t$ cat test.bash
#!/bin/bash

source ./ext.bash

echo «run main script file»

test_fn

borz@debian:~/t$ bash test.bash
run exclude bash file
run main script file
run test_fn function!

borz@debian:~/t$ chmod +x test.bash

borz@debian:~/t$ ./test.bash
run exclude bash file
run main script file
run test_fn function!
Я сказал не это. Вызвать можно что угодно, но удобнее подключать и использовать, скажем, как библиотеки функций. Вызвать как сторонний скрипт можно, но при этом теряются все преимущества. Конечно бывают и исключения когда это действительно надо.
эм… а разве в примере скрипт ext.bash не выполняет роли коллектора функций? в частности, содержит в себе функцию test_fn, которую использует скрипт test.bash
Я что-то запутался. Есть вызов типа `` он же $(), есть подключение. Я изначально сказал что второе удобнее. В чём заключается ваше мнение?
покажите ваш пример с «подключением», потому как в моём примере я подключаю скрипт ext.bash к скрипту test.bash
если же дёргать скрипт через $(), то это не подключение, а выполнение и не важно что там внутри указано — скрипт или бинарник.
Я вижу что вы именно подключаете, я это и имел ввиду под подключением. Вы неверно поняли мой первый комментарий мне кажется. Кстати у «source» есть синоним — точка. Я его всегда и использовал.
после точки можно пробел забыть поставить.

на тему «неверно поняли», то я отвечал на «На моей практике они как внешний исполняемый файл и не вызываются друг из друга поэтому и переписывать на другом языке врятли получится».
Всё верно. Они и у вас подключаются как макрос а не вызываются как внешний исполняемый файл. Вы можете подключить и вызывать функции из скрипта на языке, отличного от bash?
зачем «мне», при выполнении некого файла знать что у него в потрохах? я должен знать какие параметры подать ему на вход и что он выдаст мне на выходе.
именно про это и говорилось в комментарии.
ОК, значит мы обсуждали разные вещи =)
source, кстати, тоже башизм. Точка везде работает.
И это кстати по сути выполнение содержимого указанного файла в контексте текущего экземпляра оболочки, а не включение файла.
Поэтому и написано «как макрос» =)
Я имел ввиду определение «макрос — символьное имя, заменяемое при обработке на последовательность программных инструкций».
ну, в топике скрипты запускаются как «bash скрипт» — тут переписывание на иной от Bash язык не получится. вот если бы скрипт исполняемый был, тогда согласен — лучше без «расширения».
«Обыкновенный башизм» — очень распространенное явление. Вылавливай потом все эти $() при некорректном объявлении шелла "#!/bin/sh", за который необходимо расстреливать два раза.
$() работает и в обычном шелле, а вот многое другое из перечисленного (<(), [[ $a == $b ]] первое что в голову пришло) — уже нет
О да, как дошёл до [[ ]] vs [ ] так и думал что комментариях придет кто-то обвинит в башизме.
Но не ожидал за #!/bin/bash в шебанге.
Как вам такое поведения истинно posix echo:
$ echo -n text -n text

STANDARDS CONFORMANCE
echo: SVID2, SVID3, XPG2, XPG3, XPG4, POSIX.2

Параметры -v и -x можно задать в коде, это может быть полезно если ваш скрипт работает на одной машине а журналирование ведется на другой:

Либо явно указать опции -v -x в шебанге.

Для вас важна скорость/производительность.

Ну порой вполне приличной скорости можно добиться и с шелл скриптами. Просто не нужно пытаться обрабатывать построчно большие файлы силами шелла. Есть же замечательные sed и awk. Если нужно сделать быстро, то всё это вполне приемлимо.

ps. По поводу bash'измов, кстати, будет полезно почитать эту вики.
В баше очень много возможностей и хитрых приёмов и эту статью можно дополнять бесконечно. Например я работал над проектом с кросс-платформенными скриптами на bash и bash с этим прекрасно справлялся. Причём одной из платформ была moxa с мегаурезанной версией linux — mulinux, другой мейнфрейм от HP RTB, ещё были сервера на windows, red hat, solaris. Очень было интересно и до сих пор тот проект вызывает приятные воспоминания =)
В плане портабельности обратные кавычки ` и $( не всегда взаимозаменяемы.
Всех подробностей сейчас уже не вспомню (дело пару лет назад было).
Но заменил я в одном из наших билд-скриптов кавычки на скобочки.
Под башем — да, ура, работает.

Через день пошли багрепорты. Где-то то ли на макоси, то ли на солярке, то ли ещё на какой-то экзотике сборка сломалась из-за этой замены.
На макоси работает точно. На солярке не знаю, никогда ее не видел.
Вы не хотите этого знать.



bash до версии 2.0 и другие столь же древние шеллы не понимают $(. Не придавайте этому значения — если вам придётся работать с UNIX'ами, где есть настолько старый shell, вы об этом так или иначе вскоре узнаете сами. POSIX рекомендует синтаксис $(, бэктик ` провозглашён legacy — так что не мучайтесь понапрасну.

Ещё csh не умеет $(, но там вообще ни о какой совместимости речи не идёт.
Нужно помнить что некоторые команды не возвращают код аварийного завершения например “mkdir -p” и “rm -f”.

mkdir -p возвращает ошибку, если директорию ему создать нужно (она не существует), но прав на это не хватает.
А еще есть отличный вариант перенаправления — <<<, называется «here document»
Работает следующим образом:
$ grep "q" <<< "qweqweqwe"
qweqweqwe

То есть то, что после "<<<" — это как бы файл, то есть все, что после трех знаков «меньше» — это некоторый абстрактный файл без имени. Штука полезная иногда, но мало кто ей пользуется.
А я думал, это специфика zsh. Оказывается, bash просто не работает с таким перенаправлением без указания команды (конструкция SomeCommand <(<<< "some string") является вполне рабочей в zsh, но не в bash).
Насколько я помню `` выполняются внутри терущего процесса а конструкция $() запускает новый процесс шелла. Это может замедлить некоторые скрипты.
Это для группировок (...) vs {...}, а не подстановок.

Впрочем, я и для них ничего такого не смог воспроизвести.
Ну ладно там выполнить пару команд — ок, bash. Но когда начинаются вот все эти функции, условия, парсинг строк, циклы и т.д. — почему бы просто не написать скрипт на Python, в котором это всё проще, понятнее, работать будет с той же скоростью, куча библиотек, а знания Python пригодятся и в других целях?
Если ваш скрипт активно использует стандартные утилиты типа awk и прочего то что-то удобнее чем bash сложно найти.
Вы даже не представляете как много «библиотек» в bash.
Вполне возможно. А как обстоят дела с отладкой bash-скриптов? Можно их пройти по шагам? Поставить брейкпоинт? По ходу отладки изменить переменную? Увидеть что-то типа колл-стека? А как с многопоточностью? А вообще с компоновкой кода с использованием каких-либо паттернов?

Не то чтобы я троллил, мне правда интересно, где та граница, которая отделяет целесообразность написания bash-скрипта от использования полноценного скриптового языка?
Цитата из статьи выше:
Признаки того, что вы не должны использовать shell скрипты:

Ваш скрипт содержит более нескольких сотен строк.
Вам нужны структуры данных сложнее обычных массивов.
Вас задолбало заниматься непотребствами с кавычками и экранированием.
Вам необходимо обрабатывать/изменять много строковых переменных.
У вас нет необходимости вызывать сторонние програмы и нет необходимости в пайпах.
Для вас важна скорость/производительность.
Всем, кого интересуют не очевидные методы работы в bash советую книгу «Unix. Программное окружение». Каждый листинг в ней — произведение искусства.
Вообще, я с каждым новым крупным (несколько сотен строк) скриптом убеждаюсь снова и снова: в программировании на шелле самое главное — читаемость и при этом неперегруженность кода.

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

Более того, с очень большой вероятностью (если, конечно, бизнес вашей компании не связан напрямую с программированием на шелле) ваш код повторно использоваться не будет, зато его будет читать (и исправлять!) кто-то, кто программировать толком не умеет и правильные подходы не знает.
Это значит, что сложный скрипт просто мало кто сможет поддерживать после вас.

Проще говоря, в шелле чаще всего уместно многие вещи прибивать гвоздями на месте, уместно писать лапшу, строить негибкий код. Часто вполне правильно будет покопипастить какие-то небольшие куски кода с минимальными изменениями туда-сюда.
К нему в большей степени применимы принципы unix way и «чем меньше фич — тем лучше».
А вот уже от этого отталкиваясь можно вводить и абстракции, и дупликацию уменьшать пытаться, программу структурировать. Самое главное — ничего этого не делать в ущерб простоте. А если нужно сложную и красивую структуру сделать — вон на Python или Ruby лучше написать, они для этого и предназначены.
в программировании на шелле самое главное — читаемость и при этом неперегруженность кода.

В программировании на шелле самое главное — вовремя остановиться и понять, что лучше шелл уже не использовать
Когда вы уйдете из проекта, кто будет поддерживать ваши скрипты на питоне/руби, которые например скачивают файл и делают на него симлинк из другого места, или например фильтруют по нужному шаблону строки из файла и что-то с ними делают, или даже шлют какой-то HTTP запрос и останавливают после этого некий сервис? Как они поступят, когда нужно будет эти скрипты перенести на новый сервер, или добавить в середину скрипта еще какую-то операцию?
Даже если нужно все эти операции сделать за один проход, это не всегда повод уходить с шелла — часто нужно бывает запускать эти операции вручную по-отдельности. На шелле мы вместо одного большого скрипта просто сделаем три маленьких и враппер, который их вызывает. В случае других языков чаще всего придется выдумывать какие-то дополнительные ключи, параметры командной строки, добавлять дополнительную логику для их обработки, писать к этому хозяйству доки (как установить, как использовать).
Как вчерашний студент, который придет после вас, будет с этим всем работать?

Если какую-то сложную логику использовать, или большие длинные сценарии писать, или в вашей компании нет выделенного админа и их задачи выполняют между делом по мере надобности программисты — тогда можно посмотреть и на более правильные языки, согласен.
Возможно я сужу со своей колокольни, т.к. мне приходилось копаться и править кучу разных скриптов — bash, perl, php, немного python. Возможно, поэтому мне проще выбрать инструмент под задачу, чем использовать везде шелл.
Как вчерашний студент, который придет после вас, будет с этим всем работать?
Как вчерашний студент будет разбираться с шелл-скриптами, которые в 80% случаев работают одним способом, а в 20% — по-другому. Подумаешь, там в начале стоял какой-то set чего-то там, это же не может так влиять. Вы же не будете комментировать каждый чих в скрипте? А если будете, то тут вообще пофиг, что это — шелл или не шелл, комментарий же есть :)
Да и был я на месте такого вот студента, ничего, не сломался :) И не вижу каких-то проблем.
В случае других языков чаще всего придется выдумывать какие-то дополнительные ключи, параметры командной строки, добавлять дополнительную логику для их обработки, писать к этому хозяйству доки (как установить, как использовать).
Т.е. шелл-скрипты вы не документируете? Ключи/параметры не используете, все hardcoded? И какая разница вызвать функцию в шелл-скрипте или php?
в вашей компании нет выделенного админа и их задачи выполняют между делом по мере надобности программисты
Имхо, если в компании есть выделенные программисты и админ, то какие-то сложные задачи как раз нужно отдавать программистам, т.к. это их хлеб — писать программы.
Как вчерашний студент будет разбираться с шелл-скриптами, которые в 80% случаев работают одним способом, а в 20% — по-другому.

В каком смысле?

set я использую только -e, т.к. остальные плохо соответствуют принципу «least surprise».
Все неясные моменты, естественно нужно комментировать.

Т.е. шелл-скрипты вы не документируете? Ключи/параметры не используете, все hardcoded? И какая разница вызвать функцию в шелл-скрипте или php?

Разница в том, что на шелле у нас 3, допустим, маленьких скрипта, которые принимают 1-2 параметра и документируют сами себя через имена параметров (запускаем без параметров и получаем usage), а в случае других скриптовых языков будет один большой скрипт с десятком параметров, которые нужно выдумать, описать, реализовать их обработку.

Имхо, если в компании есть выделенные программисты и админ, то какие-то сложные задачи как раз нужно отдавать программистам, т.к. это их хлеб — писать программы.

Если бизнес компании не связан с системным программированием (а чаще всего это так), то задача управления системой, каким бы оно ни было сложным (или простым) — задача специально выделенного на это человека. Если этим занимается кто попало — результат получается грустный.
В каком смысле?
Это я как раз про неприметные set'ы :)
задача управления системой, каким бы оно ни было сложным (или простым) — задача специально выделенного на это человека.
Управление — это одно, а создание инструмента для управления — совершенно другое. Вы же используете grep, а не пишете его сами. Имхо, админ не обязан быть программистом. Если задача довольно сложная, то, опять же имхо, лучше подготовить для программиста адекватное техзадание, чем пытаться залезть туда, где ты не очень силен.
а в случае других скриптовых языков будет один большой скрипт с десятком параметров, которые нужно выдумать, описать, реализовать их обработку.
Какая разница, на чем писать эти скрипты — на шелле или на чем-то другом? Вы что, не можете в этих 3 скриптах один написать на шелле, другой на php, третий на перле? Почему обязательно если шелл — то 3 простых скрипта, а если другой язык — одна большая программа?
И кстати, вы тут говорите про «маленькие скрипты», в какой-то мере подтверждая мое «нужно вовремя остановиться» :)

Моя первоначальная посылка — каждой задаче свой инструмент. Я же не говорю «не используйте шелл вообще». Я говорю — если у вас получается нагромождение обходных путей, то возможно пора уже посмотреть на другой инструмент.
Управление — это одно, а создание инструмента для управления — совершенно другое. Вы же используете grep, а не пишете его сами. Имхо, админ не обязан быть программистом. Если задача довольно сложная, то, опять же имхо, лучше подготовить для программиста адекватное техзадание, чем пытаться залезть туда, где ты не очень силен.

Нет, конечно. Программисты выполняют задачи, которые ставит бизнес и для того, чтобы у них появилась возможность и ответственность заниматься задачами админов, вообще говоря нужно очень большую работу проделать на всех уровнях организации.
В обычных случаях, если задача довольно сложная, лучше поискать готовое решение. Либо (если его, например, нет) создать свое, но так, чтобы его смог худо-бедно поддерживать неспециалист.

Какая разница, на чем писать эти скрипты — на шелле или на чем-то другом? Вы что, не можете в этих 3 скриптах один написать на шелле, другой на php, третий на перле?

Как все это добро потом поддерживать?

Почему обязательно если шелл — то 3 простых скрипта, а если другой язык — одна большая программа?

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

И кстати, вы тут говорите про «маленькие скрипты», в какой-то мере подтверждая мое «нужно вовремя остановиться» :)

Моя первоначальная посылка — каждой задаче свой инструмент. Я же не говорю «не используйте шелл вообще». Я говорю — если у вас получается нагромождение обходных путей, то возможно пора уже посмотреть на другой инструмент.


Я изначальную фразу понял как «шелл лучше не использовать», а не как «вовремя остановиться». Видимо, отсюда и выросло недопонимание.
Разница в том, что на шелле у нас 3, допустим, маленьких скрипта, которые принимают 1-2 параметра и документируют сами себя через имена параметров (запускаем без параметров и получаем usage), а в случае других скриптовых языков будет один большой скрипт с десятком параметров, которые нужно выдумать, описать, реализовать их обработку.
Я обычно использую --help для usage (если неохота писать полноценную документацию: тогда --help будет и не для usage) и принцип garbage in — garbage out для запуска без параметров (т.е. если нет --help то скрипт может отработать как угодно, если ему требуются параметры (и как надо, если не требуются)).

Причина: --help более универсален. Если вы запустите большинство программ с --help вам покажут справку. Если нет — то как повезёт: cp --help и dd --help оба кажут справку, тогда как только cp показывает справку без --help. То же самое относится к редакторам, интерпретаторам (кроме стандартных sed и awk), различным GUI программам — все они показывают справку с --help и запускаются без аргументов. Есть и исключения, но их мало: намного меньше, чем программ с --help, но без справки при отсутствии параметров.
Я придерживаюсь описанного мной подхода по одной простой причине: нам в любом случае нужно проверять наличие всех нужных переданных параметров, так почему бы и не показывать сразу справку в этом случае?

И т.к. скрипты обычно делают что-то относительно сложное и потенциально деструктивное, я настороженно отношусь, чтобы они при запуске вообще без параметров делали что-то вообще. Хотя это от области применения, конечно, зависит.

Попробую еще добавлять проверку первого параметра на равенство -h или --help, посмотрю насколько это красиво будет. Спасибо за совет!
Если ваш проект соответствует пунктам из этого списка, рассмотрите для него языки языки Python или Ruby.

Perl?
Only those users with full accounts are able to leave comments. Log in, please.