Как стать автором
Обновить

Записки bash-скриптера. Листок первый. Сокращённый if

Уровень сложностиПростой
Время на прочтение4 мин
Количество просмотров3.5K
Всего голосов 17: ↑15 и ↓2+17
Комментарии50

Комментарии 50

ЗакрепленныеЗакреплённые комментарии

Краткая форма это не костыль, потому что это не краткая форма, а совершенно отдельный conditional оператор.
bash это отличный скриптовый язык. Логичный. Простой. Понятный. Берущий очень много из архитектуры и идеологии posix. Он совершенно не анахронизм, и если вам что-то кажется нелогичным, так это потому что по своему опыту я вижу, что мало людей пытается именно изучить баш. Подавляющее большинство считают его недоязыком, недостойным даже прочтения стандартного мануала, поэтому и пишут наощупь, потом ругаются.

Кроме того, если у других языков есть библиотеки, то в баше принято активно пользоваться консольными утилитами как gnu так и другими, а у них могут быть совершенно разные стандарты на опции и подход. Многие популярные были созданы и написаны еще до POSIX стандартизации.

Менять его не нужно - это системный скриптовый язык, созданный как шелл, для автоматизации простых рутинных действий в консоли.
Надо что-то сложнее - в линуксе из коробки доступны перл и питон. А баш удобен из-за своей глубокой совместимости. Скрипт написанный 30 лет назад будет работать и сейчас. Сможете так с питоном?

Если условие истинно, то выполняется действие_1. Если же ложно, то выполняется действие_2.

Да в том-то и дело, что это объяснение неверное. Просто неверное, вот и всё. О чем в самом конце (почему-то) и рассказывается.

Предлагаемые же решения работают, но с точки зрения нормального программиста это костыли на костылях, потому что сама краткая форма это уже костыль. Костыль, потому что тысячи и тысячи начинающих bash-пронраммистов погорели на этом, нанеся огромные убытки своим проектам и фирмам. И сколько ещё погорит. А костыль на костыле, потому что вставка лишнего эха может восприняться, как нужная когда-то команда, и быть удалена другим программистом за ненадобностью.

Рискуя нарваться на холивар и кучу минусов, все же замечу. Я писал довольно много скриптов на бэше, но как не любил, так и не люблю этот язык. Язык явно для "жрецов" от компов, какими и были в 60-80-е годы программисты *nix. Сейчас это жуткий анахронизм и надо бы давно заменить его на нормальный, легко читаемый и поддерживаемый язык (только не на такой, как PowerShell в Винде).

Почему неверное? Абсолютно верное) Оно корректно описывает реальное поведение. Но не полностью, что и стало темой данной заметки.

И нет, предлагаемые решения отнюдь не костыли. Писать return в функциях вдруг стало костылём, или что?)

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

А холивары приветствую)

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

А обратная совместимость для нового языка вообще не нужна.

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

Простите не соглашусь =)
Синтаксис питона просто похож на современные популярные языки, поэтому кажется что он более-менее очевидный.
Если взять, например, программиста на перле, то думаю ему баш будет понятен сразу =)

Ой, про Перл лучше даже не начинать.. В-)

После перла любой язык простой и понятный.

Что ни так с перлом? Писать криво и непонятно можно на любом языке. Перл позволяет писать и лаконично и запутано, но это не проблема языка.

Описывает корректно. Описывает не все возможные случаи. Странно, что программист не понимает разницы.

Неверное. && говорит что следующая команда будет выполнена, если предыдущая выполнилась успешно. || говорит что следующая команда будет выполнена, если предыдущая выполнилась неуспешно.

Они не исключают друг друга. Они позволяют перечислить цепочку команд с условием. И в цепочке может быть не action1, action2 а просто любой длины список условий.

command1 && command2 || command3 && command4 || command 5 && command6 && command7 || command8

не очень похоже на if/else? тут будет просто поочередное выполнение команд с пропуском некоторых согласно результатам выполнения и условию цепочки.

вы спорите сами с собой.

Вспомнить, что функция вообще-то всегда возвращает код ошибки, даже когда return явным образом не прописан. И тогда это код последней выполненной команды. Можно просто писать echo в качестве последнего оператора функции. Немного непрофессионально. Чревато тем, что другой программист может удалить или закомментировать этот оператор, попросту не зная о том, что на оператор неявным образом навешано дополнительное действие.

Если уж хочется что то подобное сделать (хотя не советую), то есть такой сахарок как обычное двоеточие ":" которое эквивалентно "true". Пример в интерактивном режиме, для наглядности

[user@host:~]$ false ;:
[user@host:~]$ echo $?
0
[user@host:~]$

но ведь : это не эквивалент тру. Это просто пустая команда. Используется в тех случаях, когда синтаксис требует команду, а вы ничего не хотите делать. Аналог nop в ассемблере.
То что : ничего не делает и результат команды true - это уже следствие того, что команда выполнилась успешно.
То есть это не "сахарок", а конкретная команда со своим значением.

Хотите прям сложностей, гляньте тут https://habr.com/ru/articles/340544/

То что : ничего не делает и результат команды true - это уже следствие того, что команда выполнилась успешно.

Нет такого понятия как результат команды true. Есть понятие нулевого и ненулевого кода выхода. То что я обозначил "true" это тоже встроенная команда, а не значение. Обе команды ":" и "true" (builtin, встроенные) - дают выходной код 0, что и можно использовать в некоторых случаях. Критерий "сахарка" это то, что можно писать один символ вместо четырех.

Краткая форма это не костыль, потому что это не краткая форма, а совершенно отдельный conditional оператор.
bash это отличный скриптовый язык. Логичный. Простой. Понятный. Берущий очень много из архитектуры и идеологии posix. Он совершенно не анахронизм, и если вам что-то кажется нелогичным, так это потому что по своему опыту я вижу, что мало людей пытается именно изучить баш. Подавляющее большинство считают его недоязыком, недостойным даже прочтения стандартного мануала, поэтому и пишут наощупь, потом ругаются.

Кроме того, если у других языков есть библиотеки, то в баше принято активно пользоваться консольными утилитами как gnu так и другими, а у них могут быть совершенно разные стандарты на опции и подход. Многие популярные были созданы и написаны еще до POSIX стандартизации.

Менять его не нужно - это системный скриптовый язык, созданный как шелл, для автоматизации простых рутинных действий в консоли.
Надо что-то сложнее - в линуксе из коробки доступны перл и питон. А баш удобен из-за своей глубокой совместимости. Скрипт написанный 30 лет назад будет работать и сейчас. Сможете так с питоном?

Это, все же, скорее не язык, а "клей" для автоматизации выполнения утилит операционной системы. Использую с удовольствием, но не стал бы сравнивать с питоном

Это язык, который часто используется как клей при интеграции разных систем. Но это полноценный язык.

Согласен. Но я сравниваю его с питоном только в вопросе читаемости и понимания новичком в языке. И кстати, на этом клее некоторые целые поэмы пишут. К счастью, хотя бы, не новый VS Code )

Понимание, что это не классический язык высокого уровня помогает в изучении шел программирования, например в выражении [ тест ] первая скобка - это имя программы /usr/bin/[ , тест - список ее аргументов а ] нужна только для красоты, поэтому запись [тест] без пробелов будет ошибочна, тогда как в обычных ЯП пробелы в похожем выражении не играют никакой роли

Все я понимаю. Но это не делает бэш более читабельным и более свободным от непреднамеренных ошибок.

Не совсем.
[ это внутренняя команда шелла. То, что существует внешняя, это дублирование на случай если у вас какой-то нестандартный шелл, а скрипт должен работать.
ну и не для красоты, а нормальный синтаксис.

тогда как в обычных ЯП пробелы в похожем выражении не играют никакой роли

На питоне пробовали писать?

Да, с не играющими роли пробелами в питоне я погорячился), но, все же имел ввиду использование пробелов как разделителей лексем, в похожих выражениях, например в С, можно написать if(x>y) слитно и это будет распознано синтаксическим анализатором, а скрипт на шел - обертка над командной строкой и пробелы в выражении [ тест ] обязательны. Поэтому я назвал его "клеем", пусть не обижается, он такой же классный как linux и unix, пишу на нем 90 процентов всего, что мне нужно и всем рекомендую для простых задач. Для сложных использую perl, и мечтаю найти время изучить python

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

А теперь, коллега, предлагаю на этот раз внимательно прочитать комментируемую статью, и увидеть в ней именно ту же мысль.

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

Самому не странно писать мне в комментах то же самое, что я написал в статье?

Не странно, потому что я называю и эту конструкцию, и её "решения" костылями.

Вообще из-за возможного отстрела ног, конструкцию

[ тест ] && действие_1 || действие_2

судя по моему опыту лучше не использовать даже в виде с выше предложенными фиксами. А вот конструкции типа

[ тест ] || return 1 
действие_1
действие_2
...

(внутри функции)

либо

[ тест ] && {
    действие_1
    действие_2
   ...
}

я лично использую постоянно и вам советую, если вы конечно не любите писать if then ... fi конструкции.

С какой стати логичные операторы || и && нужно сравнивать с конструкцией if else, если это разные конструкции?
То, что это conditions это одно, но это не сокращенный if else.
Интересно начинается когда делаете так:

[ тест ] && [ тест ] || [ тест ] || [ тест ] && [ тест ]

или так

[ тест ] && ([ тест ] || [ тест ]) && [ тест ]

Как бы совершенно очевидно, что на if оно уже не похоже.
Опять же, совершенно непонятно почему вы, сделав функцию с exit code 1, удивляетесь что она возвращает false для операторов проверяющих exit code?
думаете if будет работать иначе?

function return1() {
  echo все ок
  return 1
}

if return1; then
  echo все хорошо
else
  echo все плохо
fi

Можете убедиться, что код вернет "все ок - все плохо" и в if.

В данном случае нелогичность связана с тем, что вы видимо не знаете, что функции и скрипты возвращают код возврата последней выполненной команды, в результате ваша функция с такими строками в конце:

 A=нежелательное_значение
  [[ $A == 'желаемое_значение' ]] && echo делаем действие

будет возвращать exit code 1 от команды [[ ]] , у которой желаемое значение не совпало, все совершенно очевидно.

Если это ответ к предыдущему комментарию, то вы очевидно не поняли что я хочу сказать. Я не предлагаю заменять if then else конструкцию. Если где-то в коде например нужно без else cделать условную ветку, проще использовать ||, && и операторные скобки. То есть вместо

if [ test ] ; then
  ...
fi

пишем

[ test ] && {
    ...
}

Вас ввели в заблуждение действие_1, действие_2, я не должен был их вообще писать. Это просто любой код в операторных скобках.

Не, это я пишу автору статьти, который почему-то считает, что && и || это короткая форма if/else

В шелле && и || нужно ставить в один ряд с ;
То есть команды выполняются по порядку с условием или без условия, их в цепочке может быть сколько угодно, и выполниться может больше одного action

Это не автор статьи так считает, в статье буквально сказано:

Её часто называют сокращенной версией конструкции if-then-else.

Даже навскидку беглый поиск выдаёт множество руководств, где подобная запись именно так и называется.

Потому, что эта конструкция, не являясь буквально if statement, действует схожим образом.

А в статье автор как раз говорит, что с этим надо быть осторожным.

так говорить в принципе можно, если при этом знать и помнить особенности работы

И рассказывает почему.

То, что множество руководств ошибается, не значит что стоит повторять эту ошибку.
Действует она не схожим образом, поскольку конструкция просто связывает две команды (или два блока команд) условием.
И осторожничать нужно тем, кто не хочет этого понимать, и продолжает искать подтверждения своей теории "схожести с if"

ну да, множество руководств ошибается, потому что я с ними не согласен. )) весело

и нет никакой "моей теории", оттого, что вы это повторите хоть сколь угодно большое количество раз, это фактом не станет.

ну да, множество руководств ошибается, потому что я с ними не согласен

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

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

в курсе. Я вообще очень много чего про баш в курсе. Но статья не об этом.

В шелле && и || нужно ставить в один ряд с ;

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

А вот команды, соединённыё операторами && и || связаны именно логически (и также, обычно, записываются в одну строку).

Общего именно то, что все три оператора ";", "||" ,"&&" позволяют последовательно выполнить две или более команд. Последние два делают это еще и с условием.

Ну вот вы же любите ссылаться на "множество руководств". Потрудитесь поискать "выполнение цепочки команд в шелле" или "command sequence in shell". Как вообще пишут однострочники.

Я уверен в 7 из 10 результатах будет именно эта последовательность из трех операторов.

В том-то и дело, что ; - это не "оператор, который позволяет последовательно выполнить несколько команд".

Это оператор, который позволяет ЗАПИСАТЬ последовательно несколько команд. Это буквально enter, не более.

А вот && и || - как раз именно вполне подпадают под определение.

ЗАПИСАТЬ последовательно несколько команд.

И выполнить. Записать и через && можно.

С какой стати логичные операторы || и && нужно сравнивать с конструкцией if else, если это разные конструкции?

Потому что они делают одно и то же.

Интересно начинается когда делаете так: [ тест ] && [ тест ] || [ тест ] || [ тест ] && [ тест ]

Ну а не надо так делать. Причём сразу по нескольким причинам.

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

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

Как бы совершенно очевидно, что на if оно уже не похоже.

Разумеется не похоже. Я где-то утверждал, что такая конструкция похожа?

Опять же, совершенно непонятно почему вы, сделав функцию с exit code 1,
удивляетесь что она возвращает false для операторов проверяющих exit
code?

Потому что ничего подобного не было.

В данном случае нелогичность связана с тем, что вы видимо не знаете, что
функции и скрипты возвращают код возврата последней выполненной команды

ага, настолько не знаю, что вообще-то сам и написал это в статье.

функция вообще-то всегда возвращает код ошибки, даже когда return явным
образом не прописан. И тогда это код последней выполненной команды

В общем, понятно.

В принципе, есть и третье решение.

есть и четвертое решение. не использовать однострочники вообще, писать как завещал Н.Вирт (if [] ; then else fi), не жлобиться на комментарии, помнить что за [ ] скрывается /bin/test, в пределе забить на башизмы и пользоваться совместимым /bin/sh (в современных линуксах это dash).

согласен насчёт комментариев, но не насчёт всего остального. Особенно насчёт однострочников.

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

Не надо использовать, если не умеешь. Это да. Но, быть может, лучше научиться и использовать?

Однострочники не стоит использовать в скриптах

  1. Но опять таки, это просто рекомендация, а не правило, ибо многие вещи легче читаются в одну строку. Цель ведь не юзать однострочники и не избавляться от них, а сделать код более читабельным.

  2. за [] не скрывается /bin/test, там скрывается test. Не путайте внутренние и внешние команды

  3. ни [] ни || ни && не является башизмом, это стандартные команды posix шелла. С другой стороны, bash уже является стандартом де-факто во современных линуксах и за 5-10 лет возможно им станет zsh

  4. И на posix shell скорее следует равняться если работаешь с контейнерами или ембеддед системами, где каждый лишний килобайт на учете.

В bash нет логических выражений. Но есть конструкции, сильно похожие на логические выражения, и это сбивает с толку.

Честно говоря, предложенная конструкция [ условие ] && действие_1 || действие_2 выглядит изначально стремно. Я всегда использовал короткие вариант с одним действием, что понятно и почти всегда оправдано.

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

Ну, скажем так, он не выглядит очевидным именно для bash

Ну и обычно его применяют в сокращенном варианте

------------

Вы же понимаете, что указанная конструкция в реальности не равна if ... then .... else ... .fi? И поэтому у меня вообще большой вопрос, с чего вообще такая идея возникла

Ну если вы почитаете мои комментарии, то поймете что я как раз топлю за то, что это совсем не сокращение от if.
Но при этом это совершенно не стремный, а другой способ выполнить что-либо в зависимости от условия. И выглядит оно прекрасно, кратко (а не сокращенно) и логично

Это одна из первых вещей, которую учишь, начиная заниматься любым программированием. Называется operator precedence. Так вот в шелле, в отличие от, сажем, С, && и || имеют одинаковый приоритет и исполняются слева направо. TRUE && FALSE || TRUE == TRUE, TRUE || FALSE && TRUE == TRUE, TRUE || FALSE && FALSE == FALSE, etc.

Это не "записки баш скриптера", а заметки джуна на полях.

Уж простите, но всё о чем вы говорите - называется "принципы чистого кода". В данном случае хотелось бы особо подчеркнуть читаемость кода.

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

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

А для языков без статической типизации это в несколько раз более актуально.

это была очень нелепая попытка перейти на личности)

Я делаю так:

/bin/true && (/bin/false || /bin/true) || echo false

отлично, тема оказалась горячей, как я и хотел. Особо горячие головы меня же и минусуют)

Значит, очередной статье -- быть.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории