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

Все работает хорошо.

И вот однажды shell-скрипт совершает что-то совсем неправильное.

Тогда вы осознаете свою ошибку: bash, и вообще shell-скрипты, в основном, по умолчанию не работают. Если с самого начала не проявить особую осторожность, любой shell-скрипт достигнув определенного уровня сложности почти гарантированно будет глючным... а доработка функций корректности будет довольно затруднительна.

Проблема с shell-скриптами

Давайте сосредоточимся на bash в качестве конкретного примера.

Проблема №1: Ошибки не останавливают выполнение

Рассмотрим следующий shell-скрипт:

#!/bin/bash
touch newfile
cp newfil newfile2  # Deliberate typo
echo "Success"

Как вы думаете, что произойдет, когда мы его запустим?

$ bash bad1.sh 
cp: cannot stat 'newfil': No such file or directory
Success

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

Вы можете решить эту проблему, добавив set -e в начало shell-скрипта:

#!/bin/bash
set -e
touch newfile
cp newfil newfile2  # Deliberate typo, don't omit!
echo "Success"

А теперь:

$ bash bad1.sh 
cp: cannot stat 'newfil': No such file or directory

Проблема №2: Неизвестные переменные не вызывают ошибок

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

#!/bin/bash
set -e
export PATH="venv/bin:$PTH"  # Typo is deliberate
ls

Когда мы его запускаем:

$ bash bad2.sh 
bad2.sh: line 4: ls: command not found

Он не может найти ls, потому что мы допустили опечатку, написав $PTH вместо $PATH, при этом bash не жалуется на неизвестную переменную окружения. В Python вы получили бы исключение NameError; на скомпилированном языке код даже не компилировался бы. В bash скрипт просто продолжает выполняться; что может пойти не так?

Решением является параметр -u:

#!/bin/bash
set -eu
export PATH="venv/bin:$PTH"  # Typo is deliberate
ls

А теперь bash нашел опечатку:

$ bash bad2.sh
bad2.sh: line 3: PTH: unbound variable

Проблема №3: Пайпы не отлавливают ошибки

Мы думали, что разобрались с неработающими командами с помощью set -e, но это не решило всех проблем:

#!/bin/bash
set -eu
nonexistentprogram | echo
echo "Success!"

и когда мы запускаем его:

$ bash bad3.sh 
bad3.sh: line 3: nonexistentprogram: command not found

Success! 

Решение set -o pipefail:

#!/bin/bash
set -euo pipefail
nonexistentprogram | echo
echo "Success!"

Теперь:

$ bash bad3.sh 
bad3.sh: line 3: nonexistentprogram: command not found

На данный момент мы имплементировали (большую часть) неофициального "строгого" режима bash. Но и этого все еще недостаточно.

Проблема №4: Subshells работают странно

Используя синтаксис $(), вы можете запустить subshell (подоболочку):

#!/bin/bash
set -euo pipefail
export VAR=$(echo hello | nonexistentprogram)
echo "Success!"

Когда мы ее запустим:

$ bash bad4.sh 
bad4.sh: line 3: nonexistentprogram: command not found
Success!

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

Единственное исключение — это непосредственная установка переменной, поэтому нам нужно написать код следующим образом:

#!/bin/bash
set -euo pipefail
VAR=$(echo hello | nonexistentprogram)
export VAR
echo "Success!"

Теперь наша программа работает правильно:

$ bash good4.sh 
good4.sh: line 3: nonexistentprogram: command not found

Возможно, это достаточная демонстрация плохого поведения bash, но далеко не полная.

О некоторых нежелательных причинах для использования shell-скриптов

Каковы могут быть причины, по которым вы все равно захотите использовать shell-скрипты?

Плохая причина №1: Это всегда там есть!

Практически каждая вычислительная среда Unix имеет базовую оболочку (shell). Поэтому, если вы пишете какие-то скрипты для упаковки или запуска, возникает соблазн использовать инструмент, который уже там присутствует.

Дело в том, если вы упаковываете Python-приложение, то практически наверняка в среде разработки, CI и среде выполнения будет установлен Python. Так почему бы не использовать язык программирования, который по умолчанию обрабатывает ошибки?

По большому счету, практически каждый язык программирования с достаточно большой пользовательской базой содержит какую-то скрипт-ориентированную библиотеку или идиомы. В Rust, например, есть xshell, а также другие библиотеки. Так что в большинстве случаев вы можете использовать свой язык программирования вместо shell-скрипта.

Плохая причина №2: Просто пишите правильный код!

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

На практике:

  • Вы, вероятно, работаете не один; вряд ли каждый в вашей команде обладает соответствующим опытом.

  • Любой человек устает, отвлекается и допускает ошибки.

  • Почти в каждом сложном shell-скрипте, который я видел, отсутствовал вызов set -euo pipefail, и добавить его постфактум довольно сложно (обычно невозможно).

  • Не помню, чтобы я когда-либо видел автоматизированный тест для shell-скрипта. Наверняка они существуют, но встречаются довольно редко.

Плохая причина №3: Shellcheck обнаружит все эти ошибки!

Если вы пишете shell-программы, shellcheck — очень полезный способ поиска ошибок. К сожалению, его одного недостаточно.

Рассмотрим следующую программу:

#!/bin/bash
echo "$(nonexistentprogram | grep foo)"
export VAR="$(nonexistentprogram | grep bar)"
cp x /nosuchdirectory/
echo "$VAR $UNKNOWN_VAR"
echo "success!"

Если мы запустим эту программу, она выдаст "success!", несмотря на то, что у нее 4 отдельные проблемы (как минимум):

$ bash bad6.sh 
bad6.sh: line 2: nonexistentprogram: command not found

bad6.sh: line 3: nonexistentprogram: command not found
cp: cannot stat 'x': No such file or directory
 
success!

Как работает shellcheck? Он выявляет некоторые проблемы... но не все:

  1. Если вы запустите shellcheck, он укажет на наличие неполадок в export.

  2. Если вы запустите shellcheck -o all, чтобы запустить все проверки, он также укажет на проблему с echo "$(nonexistentprogram ...)". Это при условии, что вы используете версию v0.8, которая была выпущена в ноябре 2021 года. Более ранние версии не имели такой проверки, поэтому любой дистрибутив Linux, предшествующий этой версии, выдаст вам shellcheck, который не обнаружит эту проблему.

  3. В нем не предлагается set -euo pipefail.

Если вы полагаетесь на shellcheck, я настоятельно рекомендую обновиться и убедиться, что вы запускаете его с параметром -o all.

Прекратите писать shell-скрипты

В определенных ситуациях shell-скрипты вполне уместны:

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

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

  • В достаточно простых случаях, когда требуется выполнить несколько команд последовательно, без подоболочек, условной логики или циклов, достаточно использовать set -euo pipefail (и обязательно используйте shellcheck -o all).

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


Материал подготовлен для будущих учащихся на курсах "Administrator Linux. Professional" и "Administrator Linux. Advanced". Всех желающих приглашаем на бесплатные demo-заняти:

  1. «Puppet — система контроля конфигураций». На занятии будет дан обзор архитектуры puppet, его основных инструментов и методов их использования, на практике будет разобран вопрос установки, первоначальной настройки сервера и клиента, а также пример использования: настройка служб, конфигурационных файлов, установка пакетов. Регистрация

  2. «Введение в Docker». На занятии мы рассмотрим основы контейнеризации и ее отличие от виртуализации, плавно перейдем к рассмотрению самого популярного на данный момент инструмента контейнеризации Docker — узнаем, из каких основных компонентов и сущностей он состоит, и как они взаимодействуют между собой. Регистрация

Комментарии (201)


  1. amkartashov
    28.03.2022 18:40
    +32

    альтернативы какие?


    1. vvzvlad
      28.03.2022 19:18
      +6

      Написано же: Python, например.


      1. amkartashov
        28.03.2022 19:18
        +52

        bash - это не только дерьмовый язык программирования, но и универсальный удобный клей для консольных утилит. С любым другим языком либо придётся переписывать curl/sed/cat и сотни других мелких утилит, либо обмазываться popen'ами и exec'ами.


        1. ultrabloxx
          29.03.2022 00:19
          +22

          Не придётся. Для Python есть библиотека plumbum, которая не просто позволяет писать клей практически с башевской семантикой, но и позволяет это делать кроссплатформенно (например, на Windows тоже работает), или даже через SSH-соединение. Причём можно произвольно chain'ить как локальные, так и ремоутные команды, не парясь с тем, как экранировать аргументы ssh, чтобы сделать на ремоуте только то, что нужно.

          https://plumbum.readthedocs.io/en/latest/


          1. amkartashov
            29.03.2022 00:54
            +1

            Это клёво, возьму на заметку. Какие (пред)вижу проблемы:

            • надо будет pip, которым будем засорять систему (неактуально для CI/CD и других систем, где используются короткоживущие окружения)

            • придётся доверять этому пакету (раньше доверяли bash'у из пакетов вендора)

            • не уверен, что pip пакет нельзя подменить, (решается проверкой чексум)


            1. ultrabloxx
              29.03.2022 01:11

              Ну, вопрос доверия можно решить только аудитом фиксированной версии. В целом, разработчик (Tomer Filiba) достаточно известный, и помимо Plumbum у него есть много других заслуг (например, RPyC, протокол и библиотека для исполнения Python-кода на remote-серверах, например, для автотестирования или распараллеливания программ).

              В целом, если высокие требования к security - то pip-репозиторий можно поднять локально, во внутренней сети, и не брать пакеты из PyPI. И залить туда фиксированную версию, прошедшую аудит. Это решит проблему подмены пакетов.

              А если использование pip вообще нежелательно - то в принципе, установленный plumbum можно просто заархивировать, и распаковывать в site-packages питона в каждом окружении, где он нужен - насколько я помню, он написан на чистом питоне, без сишных extension'ов, так что там не нужны никакие post-install скрипты, и поэтому ставить через pip его не обязательно.


              1. ShadF0x
                29.03.2022 01:53
                +2

                установленный plumbum можно просто заархивировать

                Судя по репозиторию проекта на GitHub, там уже всё сделано так, что можно собрать самому через "python -m build", поэтому даже PyPI не нужен.


        1. ganqqwerty
          29.03.2022 00:45
          +3

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

          Не слышали про то, как можно это заранее проверить при написании скрипта, или хотя бы уж сгененировать для скрипта описание типа package.json?


          1. amkartashov
            29.03.2022 00:55
            +2

            bash - головная боль, да. Это не отменяет его универсальности и большинство этих проблем освещено в SO.


      1. igrishaev
        29.03.2022 09:28
        +19

        Серьезно? У меня образ alpine в 5 мегабайт. Прикажете тащить за собой 150 мегабайт питона? Уж если на то пошло, баш-скрипт можно сгенерировать из Питона или другого языка. Но заменять баш Питоном это адский оверхед.


        1. kingleonfromafrica
          29.03.2022 12:15
          +4

          Не все для калькуляторов кодят. Мне и большинству моих коллег даже 1,5Гб по большому счету вообще ни чего не изменит.


          1. igrishaev
            29.03.2022 12:22
            +9

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


            1. vvzvlad
              29.03.2022 13:16
              +4

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

              Если у вас на баше скрипты в пять строк, то «уйди, мальчик, не мешай», конечно, никакого плюса при внедрении питона вы не получите. Если у вас образ в продакшене в пять мегабайт, то да, добавление питона его сильно раздует (не уверен, правда, зачем может потребоваться образ в 5мб, но ладно).
              Но не проецируйте свою ситуацию на всех, пожалуйста, объявляя такое использование питона «ненужной хренью».


              1. i9i6
                29.03.2022 16:58
                -1

                если не скрипты в 5 строк - не проще ли тогда писать на настоящем яп? без утягивания за собой всего мира?


                1. vvzvlad
                  29.03.2022 18:07
                  +3

                  А у вас среднего не бывает, да? Или пять строк, или «настоящие» яп. 20 строк, как, требуют уже «настоящего» яп? А 50? А если это 50 строк, но с кучей передающихся туда-сюда значений?

                  Мир за собой никто не утягивает. Но и соглашаться с тезисом «уйдите со своим питоном, это же ЦЕЛЫЕ МЕГАБАЙТЫ кода ему требуются», я тоже не собираюсь. Просто потому, что мир не черно-белый, он градиентом:

                  Есть задачи, для которых питон излишен: сделать пайплайн уровня «cat|grep|awk>temp.txt» проще, действительно, на баше.
                  Есть задачи, которые не надо делать на баше: например, какой-нибудь простой супервизор с веб-апи и sqlite. Можно, но не надо. Любой вменяемый разработчик тут возьмет джаву/питон/плюсы/етс.

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

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


                  1. isden
                    30.03.2022 10:28
                    +1

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


                  1. 12rbah
                    30.03.2022 21:53
                    +1

                    А если это 50 строк, но с кучей передающихся туда-сюда значений?
                    Вообще на баше же можно писать функции и объявлять переменные. Многие также забывают, что bash в целом довольно универсальная штука, которую часто знают и программисты и админы и т.д., а питон обычно знают либо программисты, либо девопсы.


                1. khajiit
                  29.03.2022 20:43

                  Смысл этой штуковины: работать в хуке пакетного менеджера. Активная работа с файлами и разделами, вычленение esp на зеркалированном пуле, перемещение указанного файла в указанную папку, контроль "населенности" esp и опциональная сортировка оставшегося. Работа в связке с другими модулями — через переменные окружения.


                  На каком языке это будет лучше реализовать?
                  #!/bin/sh
                  # Check for privileges for mount and umount
                  [ $(id -u) -ne 0 ] && echo $(readlink -f $0) must be run as root && exit 1
                  #check for FAT sorting option (ESP_SORT=Some/Valid/Path/within/ESP scriptname) in ESP
                  if  [ -n "${ESP_SORT}" ]; then
                      [ -n "$(which fatsort)" ] && echo Will fatsort $ESP_SORT later || echo -e ESP_SORT option provided, but no fatsort found.\\nPlease install it with your package manager.
                  fi
                  # Payload is mandatory
                  [ ! -f "${EFI_PAYLOAD}" ] && [ ! -d "${EFI_PAYLOAD}" ] && echo EFI_PAYLOAD=/full/path/to/payload is mandatory. && exit 1
                  [ -f "${EFI_PAYLOAD}" ] && [ -z "${EFI_PATH}" ] && echo EFI_PATH must be provided for one file push. && exit 1
                  # EFI_SPACE and EFI_FREE are in MB
                  EFI_SPACE="${EFI_SPACE:-100}"
                  # check for custom ZFS pool name, else detect pool via '/' mount
                  ZFS_ROOT_POOL=$(zfs mount | grep ' /$' | awk -F'/' '{print $1}')
                  ZFS_POOL="${ZFS_POOL:-$(echo $ZFS_ROOT_POOL)}"
                  
                  set -eu
                  ESP_MOUNT=/boot/esp
                  # ESP_LINUX=EFI/Linux
                  # ESP_KEEP=5
                  ZFS_DISKS=$(zpool list -vPH "${ZFS_POOL}" | grep '^[[:space:]]*/dev' | awk '{print $1}' | sed 's/-part[[:digit:]]*$//g')
                  mkdir -p "${ESP_MOUNT}"
                  for d in $ZFS_DISKS; do
                      unset ESP
                      ESP=$(readlink -f $(sgdisk --print $d | awk -v disk=$d '{if ($6=="EF00") print disk"-part"$1}'))
                      set +u
                      [ -z "${ESP}" ] && set -u && continue
                      set -u
                      while [ $(mount | grep -c "${ESP_MOUNT}") -gt 0 ]; do umount "${ESP_MOUNT}"; done
                      fsck.vfat -pyw "${ESP}" > /dev/null
                      mount "${ESP}" $ESP_MOUNT
                      set +u
                      if [ -d "${EFI_PAYLOAD}" ]; then
                          cp -r ${EFI_PAYLOAD}/* "${ESP_MOUNT}/"
                      else
                          while true
                          do
                              EFI_FREE=$(df --block-size=1M --output=avail "${ESP_MOUNT}" | tail -n1 | awk '{print $1}')
                              if [ $EFI_FREE -lt $EFI_SPACE ]; then
                  
                                  F2D=$(find "${ESP_MOUNT}/${EFI_PATH}/" -maxdepth 1 -type f | sort | head -n 1)
                                  echo Removing "${F2D}" && rm "${F2D}"
                              else break
                              fi
                              break
                          done
                          mkdir -p "${ESP_MOUNT}/${EFI_PATH}"
                          cp "${EFI_PAYLOAD}" "${ESP_MOUNT}/${EFI_PATH}/"
                      fi
                      set -u
                      umount "${ESP}"
                      set +u
                      [ -n "${ESP_SORT}" ] && [ $(which fatsort) ] && fatsort -d="${ESP_SORT}" -t "${ESP}"
                      set -u
                  done

                  А заодно: как просто будет в реализованном разобраться…


                  1. AnthonyMikh
                    30.03.2022 23:52
                    +1

                    А заодно: как просто будет в реализованном разобраться…

                    Я вот не могу разобраться в том, что привели вы.


                    1. khajiit
                      31.03.2022 11:51

                      Это кусок хука, отвечающий за доставку payload (file or directory) в заранее заданные ESP или расположенные на тех же дисках, что и ZFS-пул. Обычно вызывается после регенерации initramfs, но также после создания/удаления клона, добавления ядер, и т.д.
                      Тасклист у него простой: проверить валидность параметров, дополнить и развернуть умолчания, найти список ESP, далее с каждым: примонтировать, выгрузить payload, проверить переполнение, отмонтировать, опционально отсортировать (именно после отмонтирования).
                      В этом экземпляре неправильная проверка (-n вместо -z) в паре мест.
                      Поскольку это хук пакетного менеджера, то интерпретируемые языки отпадают сразу: они могут сломаться в процессе обновления.


    1. ShadF0x
      28.03.2022 19:18
      +4

      Дедовские


    1. FlyHighOnTheSky
      28.03.2022 19:58
      +4

      Гугл относительно недавно представил https://github.com/google/zx


    1. xmcuz
      29.03.2022 09:09

      Go. Серьёзно!

      1. Обратная совместимость и есть во всех репозиториях.

      2. Очень простой и легко читаемый синтаксис. На порядок проще чем bash.

      3. Очень быстра компиляция, благодаря чему можно работать как с башем.

      4. Задачи по обработке больших массивов данных логов щелкается как семечки. В тысячи раз быстрее чем баш.


      1. git-merge
        29.03.2022 09:16
        +4

        4.где используется bash скорость не важна

        2.типизация мешает сильно


      1. Xop
        29.03.2022 09:41
        +1

        Забыли еще добавить, что бинари как правило без зависимостей на системные либы, что тоже очень удобно


      1. StraNNicK
        29.03.2022 11:01

        выше в комментариях было уже, но повторюсь — как в go со склейкой нескольких консольных утилит?

        Ну, т.е. реальный пример баш скрипта:
        1. curl с авторизацией и сохранением cookie;
        2. curl с выкачиванием страницы, используя cookie из п.1;
        3. множественный grep по результату, с сохранением полученных данных в переменные;
        4. формирование html файла из переменных;
        5. rsync полученного html на удалённый сервер по ssh.

        Я просто не знаю как с этим в go, интересно.


        1. unsignedchar
          29.03.2022 11:08
          +3

          3. множественный grep по результату, с сохранением полученных данных в переменные;
          4. формирование html файла из переменных;

          Даже не видел, как это сделано, но уже хочется плакать ;)


          1. StraNNicK
            29.03.2022 11:32
            +1

            да, приличные люди делают такое в Python, например.
            но в баше это в буквальном смысле 10 примитивных строчек, тем и подкупает.


            1. siziyman
              29.03.2022 15:10
              +1

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


              1. khajiit
                29.03.2022 20:46
                +1

                Write-only это регулярки… Bash неплохо читается, да и всегда можно проверить, что выполняется.


        1. Eugeeny
          29.03.2022 14:43
          +4

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

          Edit: одна из больших проблем людей, пересаживающихся с баша - неспособность отличить то, что ты хочешь (intent) от того, как ты это делаешь (means) - в основном из-за отсутствия опыта в других языках. Людям не приходит в голову, что вызов curl/cat/cut/awk - это не цель, а костыль.


          1. StraNNicK
            29.03.2022 14:59

            Так вы сейчас ровно ту же ошибку мышления демонстрируете.

            Я описал именно так, чтобы продемонстрировать — вот, мы скачиваем страницу, требующую авторизации, вот мы из неё что-то выкусываем, из этого чего-то формируем нечто другое и отправляем на удалённый сервер по ssh. Но вы увидели только curl.

            Ок, curl не нужен, а как быть с остальным? Вероятно grep тоже не нужен, а вот с rsync как?

            Я собственно и спросил — как такое делается в go?


            1. Eugeeny
              29.03.2022 15:04
              +1

              На go никогда не писал, но предполагаю что так? https://pkg.go.dev/github.com/pkg/sftp#example-package

              disclaimer: я считаю что go для таких вещей (write-once скрипты) вообще не подходит

              На питоне "без батареек" это тоже не выйдет - ssh-клиент это отдельная либа (Paramiko), но все остальное (скачать, разобрать (нормально, а не grep), сформировать) - делается нативно


              1. StraNNicK
                29.03.2022 15:09

                звучит логично, спасибо.


          1. amkartashov
            29.03.2022 15:12
            +2

            intent - запускать curl/sed/awk. Скриптописатель пишет скрипт (сценарий) запуска консольных утилит, а не сами утилиты.

            Если вы думаете, что вы вдруг напишите curl на go лучше авторов curl - да пожалуйста. Только curl - это одна утилита из тысяч.


            1. siziyman
              29.03.2022 15:20
              +3

              Если человек решает задачи исключительно в терминах "запустить команду/утилиту Х", он профнепригоден, извините. Задача - "извлечь данные с веб-страницы и сохранить их куда-то". Решаются они средствами как запуска консольных утилит, так и использования пакетов/модулей, задача программиста (ну или админа/девопса, кто он там будет) - выбрать средства реализации и корректно их использовать. Очевидно, курл, сед и авк не являются уникальными с т.з. решаемых задач.

              А на го, как и на абсолютно любом распространённом ЯП, не надо писать курл (а также сед, авк и прочие радости жизни), на них на всех сетевых клиентов - на любой вкус.


              1. amkartashov
                29.03.2022 15:26
                +1

                Вы предлагаете на каждый чих писать свою имплементацию той утилиты, которая вам в этот момент понадобилась? Вы обладаете настолько большим количеством ресурсов и времени, что вам без разницы, пролопатить man curl в поисках нужного ключа или прочитать документацию net/http и реализовать все edge cases самому? Ваши навыки убеждения помогут продвинуть ваши альтернативы курла и остальных утилит потребителям ваших "не-скриптов"?


                1. siziyman
                  29.03.2022 15:36
                  +3

                  Вы предлагаете на каждый чих писать свою имплементацию той утилиты, которая вам в этот момент понадобилась?

                  Повторяю: я не пишу утилиты, я решаю бизнес-задачи. Если для этого надо написать утилиту - бога ради, но в 99,999999% для этого можно использовать пару вызовов кода/утилит, написанных кем-то до меня. Иногда этот код даже под капотом те самые утилиты вызовет, но далеко не всегда, это просто неэффективно в большинстве случаев (что по ресурсам, что по поддержке этого кода в будущем).

                  Вы обладаете настолько большим количеством ресурсов и времени, что вам без разницы, пролопатить man curl в поисках нужного ключа или прочитать документацию net/http и реализовать все edge cases самому

                  Вы-таки не поверите, но бизнес-кода с HTTP-запросами на популярных ЯП написано совершенно невероятное количество, и как-то люди не умирают, не используя курл. Я, собственно, даже не упомню, когда при использовании распространенных HTTP-клиентов популярных ЯП, на которых я писал (притом это были по-моему 3 разных клиента на джаве и скале, и 1-2 на го), мне приходилось бы реально заморачиваться об эдж-кейсах - да, это были не скрипты, но это повышает требования к коду, а не понижает, потому что исполняется он не по ручному запросу в моём окружении.

                  Ваши навыки убеждения помогут продвинуть ваши альтернативы курла и остальных утилит потребителям ваших "не-скриптов"?

                  Навыки моего убеждения не нужны: люди буквально каждый день пишут HTTP-запросы на любом ~высокоуровневом ЯП (на невысокоуровневых тоже, но предположим, что не каждый день). Курла там под капотом обычно нет.


                  1. amkartashov
                    29.03.2022 19:14

                    в 99,999999% для этого можно использовать пару вызовов кода/утилит, написанных кем-то до меня

                    я тогда не понимаю, с чем именно вы спорите. Я именно об этом и говорю.


                    1. sshikov
                      29.03.2022 19:48

                      Ну таки вы говорите не об этом. Может вы это и имели в виду, но curl != модулю работы с http запросами. Модуль — это когда есть нормальное API, для того языка, на котором вы пишете. Когда модуль подключается одной строкой, как зависимость (чего в баше отродясь не было, поэтому в нем нельзя удобно использовать чужие модули, и нет репозитория модулей). А curl — это такой недомодуль, у которого только и есть что stdout и код возврата, по большому счету. API curl — это libcurl, или ее аналог для любого другого языка. И когда вам нужно http, это намного намного удобнее.


                      1. vvzvlad
                        29.03.2022 19:52

                        А curl — это такой недомодуль, у которого только и есть что stdout и код возврата, по большому счету.

                        Ну, правды ради, в куче ситуаций большего и не надо.


                      1. sshikov
                        29.03.2022 20:05

                        Так никто не говорит, что недомодули утилиты нельзя применять. Можно конечно. Не надо делать из них приложения, из лучше сразу делать на libcurl, или что там у вас есть в языке.


                      1. vvzvlad
                        29.03.2022 20:09

                        Ну вот нет. Если я хочу просто скачать файл и положить его на диск, curl все-таки проще будет, чем libcurl или какая-нибудь http библиотека в питоне: сначала залезь в документацию, разберись как ее вызывать и настраивать, потом получи содержимое, обработай ошибки, потом разберись как писать на диск в файл, запиши, закрой приложение, верни правильный exit code.
                        Курл задачу скачивания файла делает одной командой с парой параметров. И зачастую ну вот ничего не надо больше, чем скачать этот файл.


                      1. sshikov
                        29.03.2022 20:17

                        Скачать и положить — это все же не приложение. Против таких применений баша никто и возражать не будет.


                      1. siziyman
                        29.03.2022 20:26

                        У вас какое-то максимально нечестное сравнение.

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


                      1. vvzvlad
                        29.03.2022 20:28

                        Оно честное в рамках задачи «скачать файл».
                        Но впрочем, я нашел для питона wget, который делает тоже самое в две строки:

                        import wget
                        filename = wget.download('https://github.com/foo/bar.txt', "/etc/") 


                      1. siziyman
                        29.03.2022 20:30

                        Да не честное, потому что "а вот для курла нужна вот эта структура команды и вот эти параметры" вы почему-то взяли за заранее известное (а настройки клиента в ЯП - нет), да и про обработку ошибок в курсе умолчали, а для ЯП - сказали, что с ней надо разобраться. :)


                      1. vvzvlad
                        29.03.2022 20:36

                        Ну мы же не в идеальном мире, где люди смотрят на абстрактную сложность освоения инструментов с нуля, а не оценивают ее суммарно с имеющимися навыками. Люди, которые пишут скрипты и прочитали эту статью, скорее всего уже знают, как пользоваться курлом до уровня «скачать файл». А как вызвать http-библиотеку в питоне я каждый раз гуглю или смотрю в коде, хотя делаю это не в первый раз.

                        да и про обработку ошибок в курсе умолчали, а для ЯП — сказали, что с ней надо разобраться. :)

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

                        В питоне для этого надо писать дополнительную конструкцию, которая ловит исключения. Оно более гибкое, но более сложное.


                      1. unsignedchar
                        29.03.2022 20:56

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

                        2 строки ;) (3 вместе с import)

                        Не так страшен тот питон, каким он кажется.

                        Документацию в любом случае нужно читать; man curl совершенно не маленький.


                      1. amkartashov
                        29.03.2022 19:57

                        если у меня задача написать приложение, которое работает по http, то bash-скрипт с курлом у меня может появится максимум на этапе прототипирования. Если у меня задача автоматизировать ручные действия типа сборки или установки софта в предсказуемом окружении, то я не буду заморачиваться с изучением net/http или requests, а просто вызову wget/curl.


                      1. sshikov
                        29.03.2022 20:12

                        Ну, я в общем с этим согласен. Я скорее вот о чем:

                        Вы предлагаете на каждый чих писать свою имплементацию той утилиты, которая вам в этот момент понадобилась?


                        Речь не о том, чтобы писать свою реализацию curl/утилиты. Речь о том, что в других языках, не в баше, принято писать и использовать такое как модуль. libcurl, если угодно.
                        Потому что интеграция на уровне API нескольких разных модулей, кроме http, обычно получается гораздо проще.

                        Если у меня задача автоматизировать ручные действия типа сборки или установки софта в предсказуемом окружении

                        Ну таки да. Вопрос в предсказуемости. Вот как только мне стало нужно проверить это самое окружение, так я сразу ухожу от bash и консольных утилит к API, если такой мне доступен. Как только мне нужно работать с чужим REST API, я обычно не буду это делать curl-ом, а таки возьму даже не http модуль, а что-то еще поудобнее. И мне кажется, автор все-таки примерно про это пишет — если вы пишете на баше, нужно себе четко представлять ограничения этого выбора. Они есть, и они хорошо известны.


                1. Eugeeny
                  29.03.2022 15:54
                  +2

                  Вы удивитесь конечно, но 99.999% интернета не на libcurl работает


                  1. vvzvlad
                    29.03.2022 18:10
                    +1

                    Они что, все написали реализацию curl лучше авторов curl?! ©


                    1. Eugeeny
                      29.03.2022 18:28

                      Это называется "HTTP" а не curl.

                      сарказмодетектор сломался


                  1. amkartashov
                    29.03.2022 19:19

                    Как-то не получилось у тебя мысль донести. curl это пример. 10 строк на баше, написанные за минуту могут заменить миллион строк на си, на которые потрачен человеко-век.


                    1. vvzvlad
                      29.03.2022 19:52

                      Если что-то пишется на баше в одной строке и на это потрачен человеко-век, то скорее всего это вызов какого-то приложения. Если это вызов какого-то актуального приложения, то либо у этого приложения есть библиотека (как libcurl), к которой есть адаптер на распространенные языки, либо этот функционал так или иначе затащен во все языки готовыми либами.
                      Мне сложно представить что-то, что доступно только в виде бинарника, вызываемого в баше, но недоступно, скажем, в том же python.
                      Все эти grep/awk/sed/cut — доступны и вовсе без библиотек (а зачастую и не требуются, потому что можно получить выхлоп команд в json). curl/wget заменяются http. Пайплайны — вон выше plumbum предложили. Что еще такого незаменимого в баше обычно используется?


                      1. amkartashov
                        29.03.2022 20:11

                        Если что-то пишется на баше в одной строке и на это потрачен человеко-век, то скорее всего это вызов какого-то приложения

                        Я думал, это очевидно. Как оказалось нет.

                        Незаменимости никакой нет. Просто подумай, как долго ты будешь на go/python/etc писать аналог

                        version=1.2.3 && wget https://github.com/some/thing/releases/download/${version}/thin_linux_amd64 -O /usr/local/bin/thing && chmod +x /usr/local/bin/thing

                        , а главное, зачем? И как ты этот код на go/python будешь доставлять до места применения?


                      1. vvzvlad
                        29.03.2022 20:22

                        Я бы ээ, попросил вас на вы обращаться, мы с вами на брудершафт не пили.

                        Этот код пишется в те же несколько строк (у меня ушло вот 6 минут, чтобы найти как это сделать в гугле):

                        import wget
                        import os
                        version = "1.2.3"
                        url = 'https://github.com/some/thing/releases/download/{ver}/thin_linux_amd64'.format(ver=version)
                        path = "/usr/local/bin/"
                        filename = wget.download(url, path) 
                        os.chmod(path+filename, stat.S_IXUSR)


                        Можно чуть короче, можно чуть длиннее. Можно не wget, а что-то более низкоуровневое. Зачем — зависит от того, что именно я делаю. Если вся задача — «установить docker-compose», то да, питон не нужен. Если задача чуть сложнее и включает в себя еще десяток команд — то питон внезапно оказывается более сопровождаемым и менее склонным к сюрпризам.

                        Так все-таки, эээ… что такого есть наработанного в баше, что нельзя заменить питоном?


                      1. amkartashov
                        29.03.2022 20:32

                        np, если у вас аллергия на местоимения...

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

                        Этот код пишется в те же несколько строк (у меня ушло вот 6 минут, чтобы найти как это сделать в гугле):

                        А вы его проверили? А вы подумали, как его запускать на целевой системе?

                        Нельзя баш заменить питоном там, где нет питона, но есть баш.

                        Можно ещё с этим поупражнятся https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 - там всего то надо заменить curl/gpg/sudo


                      1. vvzvlad
                        29.03.2022 20:55

                        Нельзя баш заменить питоном там, где нет питона, но есть баш.

                        А, э, простите, я разве утверждал обратное?
                        Если питона нет, то на питоне писать не получится. Если в системе только sh, то не получится писать и на bash. Если в системе нет posix-вызовов, то там не получится запустить тот софт, который написан под linux. Если под систему нет stdlib, то там не получится запустить линукс вообще. Есть много ограничений на разных системах, с которыми приходится мириться.
                        Но в данном конкретном случае ваше возражение звучит как «а что ты будешь делать со своим питоном на системе где отсутствует питон, а? а? а?!». Ну ничего не буду делать. Как и вы с bash/sh на системах, где отсутствует то множество команд bash/sh, а есть только обрезанный busybox.

                        Но мы же не говорим про все системы в мире, мы обсуждаем возможную замену bash на python, допуская саму теоретическую возможность замены (т.е. подразумевая, что система нормальный arm/x86 с нормальным современным линуксом с возможностью ставить пакеты), чтобы обсуждение было предметным, а не скатывалось в попытки померяться ограниченностью систем оппонентов (—А у меня оперативки 4мб! —А у меня процессор без FPU!).

                        И вот в случае возможности запуска python как такового, уже можно обсуждать, а можно ли этот скрипт заменить на скрипт на питоне. Вы привели пример скрипта, я показал, что он переписывается на питоне, было бы желание. Думаю, не сильно погрешу против истины, если скажу, что и get-helm-3 тоже прекрасно переписывается на питоне. GPG — это python-gnupg, curl — Requests или wget и так далее. Заменить — можно.

                        А уж вопрос того, надо ли это — зависит от задач. Развесистый скрипт конфигурирования окружения в докере можно и заменить. Инсталлятор какого-то софта, который должен запускаться где угодно — может, и не стоит заменять.


                      1. edo1h
                        30.03.2022 13:31

                        Как и вы с bash/sh на системах, где отсутствует то множество команд bash/sh, а есть только обрезанный busybox.

                        а он точно обрезанный? не натыкался на скрипты на posix shell, которые busybox не может запустить.


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


                        Думаю, не сильно погрешу против истины, если скажу, что и get-helm-3 тоже прекрасно переписывается на питоне. GPG — это python-gnupg, curl — Requests или wget и так далее. Заменить — можно.

                        заменить-то можно, но доставка этого скрипта становится проблематичнее. нужно обеспечить наличие питона и кучки библиотек; curl же с gpg есть почти на каждой системе.


                      1. Eugeeny
                        29.03.2022 20:55
                        +1

                        Нечего и упражняться, скрипт - наполовину мусор, нужный чтобы все не развалилось из-за незаэскейпленных символов в именах, отсутствия curl (вот это неудобно вышло, да?)

                        И все это в виде дикого полотна в 120 символов шириной с пятикратным повторением одних и тех же имен


              1. edo1h
                30.03.2022 00:54

                не надо писать курл (а также сед, авк и прочие радости жизни), на них на всех сетевых клиентов — на любой вкус.

                реальный пример: понадобилось мне проверить обновился ли файл на сервере, если обновился — скачивать и запускать некоторое действие


                HTTPCODE=$(curl "$URL" -o "$FILETMP"  -z "$FILE" --silent --location --write-out '%{http_code}')
                if [ "$HTTPCODE" = "200" ] ; then
                    mv "$FILETMP" "$FILE"
                    # do something
                fi

                в голанг же http.Get из net/http не умеет дополнительные хидеры. реализовать можно, но это будет не пара строчек, как на sh+curl


                1. unsignedchar
                  30.03.2022 07:51

                  r = request.urlopen(url)
                  if r.code == 200:

                  open (filename,"w).write(r.read())

                  Где-то так ;) Те же 3 строчки, но на Python. КМК, ничем не хуже по понятности кода.

                  ЗЫ: редактор на мобильном - днище.


                  1. edo1h
                    30.03.2022 10:45

                    нет, не три строчки.
                    надо прочитать дату файла, засунуть её в if-modified-since в определённом формате.


                    и у меня речь шла про golang, а не про python, в golang у http.Get нет параметра headers. если идея обернуть мелкую утилитку на golang в шелловский скрипт ещё как-то укладывается в моей голове, то идея обернуть её же в питоновский скрипт мне не нравится.


                    1. unsignedchar
                      30.03.2022 11:48

                      К сожалению, чтобы понять что именно делается в вызове curl, нужно помнить наизусть портянку man curl. Write only code во всей красе.


                      1. khajiit
                        30.03.2022 11:53

                        А чтобы понять, что делается в коде произвольно взятой функции на произвольно взятом ЯВУ надо знать этот ЯВУ, используемый фреймворк, а зачастую еще и структуру и особенности тех штук, к которым есть обращения из этого кода. Write only code во всей красе, говорите? ;)


                      1. unsignedchar
                        30.03.2022 12:01

                        Если я вижу обращение к методу headers() - я догадываюсь, что тут что-то связанное с заголовками в http. Если я вижу ключ -z .. ?


                      1. khajiit
                        30.03.2022 12:33

                        … то он сопровождается timecond, и его значение становится предельно ясно из контекста так же, как http.headers()?


                        $ curl --help | grep '\-z'
                         -z, --time-cond <time> Transfer based on a time condition

                        На самом деле, ваше утверждение:


                        чтобы понять что именно делается в вызове curl, нужно помнить наизусть портянку man curl

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


                      1. unsignedchar
                        30.03.2022 12:40
                        +1

                        Не буду сильно спорить.. обычно не нужно помнить все тонкости всех библиотек, чтобы увидеть, что вот здесь мы берём значение заголовка и с чем то сравниваем. А вот что такое Transfer based on a time condition ? В каком из форматов времени оно задаётся?


                      1. edo1h
                        30.03.2022 12:59

                        В каком из форматов времени оно задаётся?

                        слушайте, ну вы явно придираетесь, видно же, что в качестве параметра мы передаём имя локального файла


                      1. khajiit
                        30.03.2022 13:06
                        +1

                        Кстати, не только.
                        Вот что говорит справка:
                        Взять дату из файла, загрузить если удаленный файл новее:
                        curl -z local.html http://remote.server.com/remote.html
                        Взять дату из файла, загрузить если локальный файл новее:
                        curl -z -local.html http://remote.server.com/remote.html
                        Указать метку непосредственно:
                        curl -z "Jan 12 2012" http://remote.server.com/remote.html


                      1. edo1h
                        30.03.2022 13:59
                        +1

                        Кстати, не только.

                        да, я в курсе, что есть несколько вариантов использования опции -z. имелось в виду, что в данном случае из контекста понятно какой вариант нужен (и да, именно он и используется)


                      1. khajiit
                        30.03.2022 14:24

                        Да, простите, утерял контекст.


                      1. khajiit
                        30.03.2022 12:59

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


                        А вот вопросами формата и типичного использования во время написания кода вам придется задаться и при подключении libcurl из языка, так что тут паритет.
                        В случае же заголовков все довольно прозрачно:


                        $ curl --help | grep header
                         -D, --dump-header <filename> Write the received headers to <filename>
                             --etag-save <file> Get an ETag from response header and save it to a FILE
                             --haproxy-protocol Send HAProxy PROXY protocol v1 header
                         -H, --header <header/@file> Pass custom header(s) to server
                         -i, --include       Include protocol response headers in the output
                             --proxy-header <header/@file> Pass custom header(s) to proxy
                         -J, --remote-header-name Use the header-provided filename
                             --styled-output Enable styled output for HTTP headers
                             --suppress-connect-headers Suppress proxy CONNECT response headers

                        Но, опять же, изучать надо как выхлоп man или --help так и справку по libcurl. И, точно так же, часто используемые ключи/параметры запоминаются и в дальнейшем обращения к справке уже не требуют — вплоть до написания блоков кода по памяти, даже без использования подсказок IDE.


                      1. edo1h
                        30.03.2022 12:57

                        Если я вижу обращение к методу headers() — я догадываюсь, что тут что-то связанное с заголовками в http. Если я вижу ключ -z… ?

                        хорошо, добавляем комментарий «download if remote version is newer than local». всё равно вариант с curl останется заметно компактнее.


                        К сожалению, чтобы понять что именно делается в вызове curl, нужно помнить наизусть портянку man curl

                        зачем помнить наизусть? у вас man не работает?


                      1. unsignedchar
                        31.03.2022 08:13

                        вариант с curl останется заметно компактнее.

                        В 21 веке компактность исходников очень редко важна.

                        у вас man не работает?

                        Одно дело писать код, поглядывая в документацию. Другое - когда без документации даже прочитать невозможно. Write only code - по возможности лучше этого избегать.


        1. 12rbah
          30.03.2022 21:56

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


      1. amkartashov
        29.03.2022 11:30
        +4

        где сейчас используются bash скрипты:

        • всякие ci/cd системы (circleci, github actions)

        • инит скрипты в контейнерах (entrypoint.sh, или даже сырой скрипт в манифестах k8s)

        • небольшие врапперы (от алиасов и функций в bashrc до небольших скриптов в $HOME/bin)

        • системы сборки (make)

        • configuration management (ansible)

        • системные скрипты для пихания в крон (бэкапы там) в системд

        При этом очень часто код этих скриптов попадает в git.

        Если заменить это на Go (замечательный язык, сам пользуюсь), то возникнут всякие сложности (всё решаемо, но всё-таки), например:

        • oneliner типа `curl url | jq .. || echo "can't"` превратится в портянку с main.go, +go.mod

        • в git придётся либо пихать бинарник, либо добавлять компилятор на стороне использования, кэшировать там зависимости и тд, следить за платформой

        • что-то по-быстрому поменять уже не получится

        • придёт сисадмин, который go не знает, ты его учить будешь? Либо будешь добавлять в вакансию требования знания go?

        • как ты код на go запихаешь в userdata AWS EC2 инстанса?

        Про скорость уже написали.

        Задачи типа обработки логов.. кто в своём уме будет писать это на баше?


        1. khajiit
          29.03.2022 11:39
          +1

          Кстати, вспоминается, кто-то на хабре писал, что zgrep'нуть пакованные json (и скормить jq, если надо) у них быстрее, чем писать запрос в эластик.


          1. amkartashov
            29.03.2022 11:44

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


        1. saterenko
          29.03.2022 12:22

          Я обрабатываю логи в bash-е. Но логи у меня plain, а обрабатываются они типа grep 'ERROR'|grep '2022-03-29 10:00' с отправкой количества сообщений в graphite, на который настроена grafana с отображением всего этого и алертингом. Что-то типа:

          #!/bin/bash
          now=`date +%s`
          min=`date --date="1 min ago" "+%Y/%0m/%0d %0H:%0M:"`
          cnt=`grep "$min" /var/log/nginx/router.error.log|wc -l`
          echo "scripts.nginx.router_error $cnt $now" | nc -q0 graphite 2003


      1. jingvar
        29.03.2022 21:37

        А как предлагаете на Го код править по месту?

        Привезли бинари, программа фейлится и что делать?

        на баше или питоне навтыкал принтов подебажил, поправил код и поехали

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


  1. stantum
    28.03.2022 18:58
    +1

    Спасибо за хорошую подборку ошибок, будет полезна. В статье не хватает положительных примеров. Есть упоминание о питоне, еще пара слов - о "чем-то менее ломком". Конкретнее бы не мешало указать возможности.


    1. sshikov
      28.03.2022 19:06
      +5

      Дело в том, что возможностей на самом деле вагон, но: «иногда у вас действительно нет гарантий, что доступен другой язык программирования». А так как бы скрипты можно писать на чем угодно, практически, из того что у вас есть под руками. Я думаю что число подходящих так или иначе языков исчисляется десятками. Все что можно оформить как #!, сгодится. Ну вот я на груви писал — но вас уговаривать не буду, брать надо то, что вы знаете, что вам удобно.


    1. vkni
      28.03.2022 19:33

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

      shell, python, ocaml, idris

      Вместо Камла можно взять что угодно другое со статической типизацией, pattern matching и сборщиком мусора.

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


      1. ganqqwerty
        29.03.2022 01:01

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


        1. 0xd34df00d
          29.03.2022 01:51
          +1

          Так оно предлагается ведь не для любого проекта, а в зависимости от объёма (и сложности, я бы добавил).


          Трёхстрочные скрипты без особой логики — sh/bash. Простые скрипты с нетривиальной логикой — питон (ну или я на хаскеле с turtle и подобными успешно что-то делал). … Система управления реактором — условный идрис.


  1. Ares_ekb
    28.03.2022 19:23
    +5

    Проблема №4: Subshells работают странно

    Для этого есть ещё один ключ -E

    Я обычно использую такие настройки:

    set -Eeuo pipefail

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

    На мой взгляд, основные проблемы с кросс-платформенностью. Начиная с того, что непонятно как лучше: /bin/bash, /usr/bin/bash, /usr/bin/env bash На Mac OS могут быть недоступны какие-нибудь команды типа readlink. Windows - это вообще отдельная история, там по хорошему все команды нужно писать в виде

    call команда || exit /b 1

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

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


  1. DDUH
    28.03.2022 19:27
    +21

    Ошибок конечно приведено достаточно, но в реальности правильно написанный shell-скрипт намного лучше альтернатив на Python или чём-то другом.


    1. siziyman
      28.03.2022 22:07
      +7

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

      И да, инструмент, на котором вероятность у конкретных имеющихся разработчиков допустить ошибку меньше, выгоднее в 9 случаях из 10, чем разбирать всю идиоматику работы с ещё одним инструментом (а sh и правда неимоверно проклятый во многих аспектах - и да, даже сам Стивен Борн сказал, что писать на нём что-то сложное - плохая затея в современном мире, и не надо этого делать).


      1. elve
        29.03.2022 11:22
        +2

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

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

        Как пример - получение токена jfog на баше это одна строка.


        1. siziyman
          29.03.2022 13:11

          И предлагается тем кто неправильно писал на bash, писать неправильно на других языках (ну а если по другому не умеют пока).

          Вы как-то ну очень in bad faith читаете то, что я написал. :)

          Проблема как бы в том, что обычно хотя бы на одном другом языке человек пристойно писать умеет (если уж он программист), а шелл-скрипты - совсем нет, там на очень многие вещи надо учиться смотреть заново. А ещё знания и опыт между джавами-го-пхп на всякие там пайтон и груви переносятся просто лучше, чем на sh. Не потому, что эти языки лучше (то вопрос холиварный и в общем-то бесполезный), а потому, что друг к другу они сильно ближе, чем к sh.

          почему-то так выходит, что питоновский скрипт на 30 строк меняется на башевский в 5

          Если питоновский тридцатистрочный скрипт могут эффективно (т.е. без багов и с меньшими временными затратами и на чтение, и на модификацию) поддерживать 10 человек, а башевский пятистрочный - 2, то бизнесово правильным решением будет иметь 30 строк на питоне, а не 5 строк на баше.

          Ни у одного известного мне программиста зарплата ни прямо, ни обратно от количества строк кода не зависит, потому не понимаю, почему это проблема. :)

          либо вообще проще сразу на Groovy написать

          Да ради бога! Это куда лучше с точки зрения понимания окружающими в типичном современном айти-коллективе, чем баш.

          Как пример - получение токена jfog на баше это одна строка.

          См. выше - не понимаю, откуда такая одержимость количеством строк. :)


          1. elve
            29.03.2022 14:28
            +2

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

            Если умеет, то может и проще по привычному написать. На самом деле зависит от многих условий (к примеру наличие нужно интерпретатора и всех его библиотек правильных версий на целевом сервере).

            Однако давайте только посмотрим на примеры ошибок из статьи, по которым предлагается на каждую shell-команду монстрячить скрипт на языке высокого уровня, т.к. sh это "фу, бяка" =).

            Человек, который пишет вот такой код

            touch newfile
            cp newfil newfile2  # Deliberate typo, don't omit!
            echo "Success"

            или вот такой:

            #!/bin/bash
            echo "$(nonexistentprogram | grep foo)"
            export VAR="$(nonexistentprogram | grep bar)"
            cp x /nosuchdirectory/
            echo "$VAR $UNKNOWN_VAR"
            echo "success!"

            и на питоне его напишет также. И на Go. И на Groovy. Но в то же время он будет называть себя программистом.


        1. sshikov
          29.03.2022 19:55

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

          Например, у баша по сути нет модулей, подключаемых из репозитория (maven, npm, pip и т.п.). А у питона уже есть. И у груви тоже. Следовательно, ваши 30 строк питона могут подключить библиотеку, э… ну скажем pandas, и опаньки, вы резко вылезли за возможности того, что можно написать на баше, и при этом ваш код стал всего-то строк 50 содержать. А у меня, к примеру, на текущем проекте, куча вот таких вот скриптов, написанных на скале, подключающих спарк, и обрабатывающих большие данные. Как говорится, удачи, такое на баше наваять. В этом и выгода.


          1. elve
            29.03.2022 22:29
            +1

            Я рад за вас, но shell-скрипты (в т.ч. sh/bash) это для перетаскивания файлов, какой-то пакетной обработки текстовых файлов и прочие примитивные действия. Автоматизация рутинных операций по администрированию системы. Ну и в нашем случае деплой или запуск сборщика. Никакой бигдатой тут и не пахнет =).

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


            1. sshikov
              29.03.2022 22:55

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

              > для раскладывания файлов по папкам нужно модули обязательно качать и бигдату
              Не, вы немного не так поняли. Я скорее за другое. У меня есть scala в качестве баша. Ну, как у автора резонно замечено: «иногда у вас действительно нет гарантий, что доступен другой язык программирования». А если они есть — я в определенном окружении, и знаю, что у меня будет то-то и то-то, всегда в наличии.

              И даже пусть у нас рутинные вполне действия — по перетаскиванию файлов, но в рамках HDFS. Ну то есть, это не бигдату для раскладывания файлов, а наоборот, раскладывание файлов для бигдаты. Операции вполне обычные, но файловая система слегка специальная, и еще надо иногда какой-то REST дернуть, или что-то еще сделать, типа SQL запрос выполнить.

              И тут уже вопрос стоит чуть иначе, выбор между башем и скалой для вполне рутинных действий типа копирования и раскладывания становится иногда вполне очевидным в пользу скалы. Хотя варианты, когда все делается утилитами (и SQL, и рест, и перекладывание файлов) я тоже вижу регулярно. Ну скажем так — мне они не нравятся, и я скалу все чаще для такого выбираю. Но уж выбор инструмента, который мне или вам нравится — он вообще всегда субъективный, и зависит и от личного опыта в том числе, и тут нельзя сказать, что вообще лучше и что хуже.


              1. elve
                30.03.2022 18:03

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


      1. maledog
        29.03.2022 11:25
        +2

        Можно подумать на python, php или golang пишут только профессионалы. Иногда откроешь код и за голову хватаешься.


        1. siziyman
          29.03.2022 13:13
          +3

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

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


          1. maledog
            29.03.2022 14:32

            Я к тому, что если человек пишет скрипт и не задумывается о проверке результатов того или иного действия или о том что нужно объявить переменную и проверить опечатки, точно так же будет писать и на любом другом языке.
            Здесь уже были не раз разборы проверки программ анализаторами кода. А пару месяцев назад я так же спорил с другими разработчиками на go по поводу того, что нехорошо передавать параметры web без проверки и санации в shell. То же самое с конкатенацией при написании SQL-запроса.
            И можно подумать среди разработчиков мало знающих sh/bash/cmd/bat. Как правило с них начинают.


            1. unsignedchar
              29.03.2022 14:43

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


              1. maledog
                29.03.2022 14:48
                +1

                Вам же в статье написали, что в shell такое тоже возможно. Там где не удастся поработать с неинициализированной переменной всегда можно накосячить например с областью видимости или указателями.

                Опять же. Есть ShellCheck.

                #!/bin/bash
                export PATH="venv/bin:$PTH"  # Typo is deliberate
                ls
                $ shellcheck myscript
                 
                Line 2:
                export PATH="venv/bin:$PTH"  # Typo is deliberate
                                      ^-- SC2153 (info): Possible misspelling: PTH may not be assigned. Did you mean PATH?
                
                $ 

                Или вы думаете, что ваша IDE ошибки в коде находит "волшебным образом"?

                Вот моя "любимая" ошибка о которой не предупреждает компилятор:

                package main
                
                import "fmt"
                
                func main() {
                	var s []int
                	s = append(s, 1)
                	// .....
                
                	// .....
                	fmt.Println(s[1])
                }
                


              1. Borz
                29.03.2022 14:55

                для Bash тоже есть IDE, который так же подсвечивает подобные ошибки


                1. unsignedchar
                  29.03.2022 15:56

                  Название?


                  1. Borz
                    29.03.2022 16:05

                    лично у меня это семейство JB (IDEA, GoLand, etc) + плагин BashSupport


                  1. maledog
                    29.03.2022 16:24

                    shellcheck плагином идет ко многим IDE. Как в примере выше.


            1. siziyman
              29.03.2022 14:59
              +2

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

              Если мне не нужно какое-то прям error recovery в самом скрипте, то всё проще: я привык к поведению "есть ошибка - упадёт, будем разбираться". Потому и не задумываюсь о проверке результатов. Тот факт, что в шелле не так - один из примеров того, как разные парадигмы и изначальные предположения при дизайне языка приводят к тому, что переход с одного на другое делает очень больно.

              или о том что нужно объявить переменную и проверить опечатки

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

              я так же спорил с другими разработчиками на go по поводу того, что нехорошо передавать параметры web без проверки и санации в shell.

              Не очень понял, как go, web и shell в одном предложении вообще связаны все втроём, но допустим. Как я уже сказал выше - плохие разработчики есть в любой экосистеме, с этим никто не спорит. Просто переход в другую экосистему порождает новые классы ошибок связанные с тем, что язык задизайнен иначе, притом в случае с sh как раз-таки довольно радикально в некоторых местах (не-падение при ошибочном результате - это отличие, которое радикально влияет на то, как ты с каждым вызовом чего-то работаешь, да), а используется там, где последствия могут быть наоборот куда более плачевными (не экранировал переменную/путь с пробелом и удалил не то, что планировал, например), чем если косякнуть при работе с данными в памяти в других ЯП в бизнес-задачах.

              И можно подумать среди разработчиков мало знающих sh/bash/cmd/bat. Как правило с них начинают.

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

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


              1. maledog
                29.03.2022 15:38

                Если мне не нужно какое-то прям error recovery в самом скрипте, то всё
                проще: я привык к поведению "есть ошибка - упадёт, будем разбираться".
                .....
                (не-падение при ошибочном результате - это отличие, которое радикально влияет на то, как ты с каждым вызовом чего-то работаешь, да)

                И наоборот, кому-то может понадобиться, чтобы работа была продолжена. Часто бывает, что ошибка не критичная и не стоит "паники".
                Выбор shell/ЯП вопрос выбора подходящего инструмента для решения конкретной задачи.


                1. siziyman
                  29.03.2022 15:55
                  +1

                  И наоборот, кому-то может понадобиться, чтобы работа была продолжена. Часто бывает, что ошибка не критичная и не стоит "паники".

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

                  Ну и да, реализовать поведение "прочитал - проверил ошибку - поигнорил" тоже стоит обычно примерно ничего, если язык инструменты обработки ошибок предлагает. А поведение по умолчанию должно быть именно тем, которое makes sense в большем количестве юзкейсов - для скриптов это как раз скорее fail-fast.


    1. Stas911
      29.03.2022 01:22
      +1

      До тех пор, пока автор его поддерживает.


      1. maledog
        29.03.2022 14:36
        +1

        Что мешает переписать? Откуда такое бережное отношение к чужому коду? Вроде "мне не ясно до конца что оно делает - лучше я не буду это трогать пока работает".

        Да и на любом языке можно написать код так, что никто из знакомых с языком не поймет что оно делает. Как быть с поддержкой такого кода?

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


        1. Stas911
          30.03.2022 03:32

          Внедрять coding guides и выгонять таких людей, пока не слишком поздно.

          Жизнь слишком коротка, чтобы разгребать чужое г...


          1. maledog
            30.03.2022 21:34
            -1

            Тут есть тонкая грань. Зачастую не говнокодят и пишут хороший код люди, которые не способны написать ничего нового. Через некоторое время у вас в команде окажется много старательных и исполнительных людей, но вот развивать и расширять возможности продукта будет невозможно. Вы выдаете им задание на новую фичу, они старательно берутся за дело, в процессе расшибут себе лоб, несколько раз уронят готовый продукт, а результат будет крайне незначительный, зато все в красивом коде, тестах, etc...

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

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

            Внедрять coding guides

            Сам я одиночка. Но когда работал в больших компаниях по большей части "coding guides" заключалось в "форматируем табами или пробелами" или именуем функции венгерской нотацией или "верблюжатками". Но внутри соблюдая правила можно было так же говнокодить дальше. просто сопровождать все комментариями в стиле КО: func copy_file() //копируем файл.


            1. Stas911
              30.03.2022 22:15
              +1

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


    1. xsevenbeta
      29.03.2022 09:59
      +1

      С башем нужно уметь вовремя остановиться и сказать себе, что на питоне это будет работать быстрее и эффективнее (и потратит меньше времени на написание и отладку, кстати). Обожаю оба этих языка.


  1. Cheater
    28.03.2022 19:55
    +16

    Автор я думаю имел в виду sh, а не bash? Присутствие Bash никто не гарантирует. Python - тем более.

    Деплой через шелл-скрипты конечно ужасен, но:

    a) Дело скорее не в проблемах *sh, а в том, что использование ЛЮБОГО императивного языка для деплоя - это плохо из-за его гибкости - на шелле можно написать ВСЁ. Ровно как и на питоне и на перле и на чём угодно. Ограничить скрипты деплоя только "легальными" действиями можно только через использование декларативного языка с жёсткими ограничениями.

    б) Это чудо что все юниксы хоть как-то смогли договориться о гарантии наличия штатного интерпретатора. Сколь бы плох он ни был, другого такого вряд ли будет. К попыткам зумеров сделать таковым Python я отношусь крайне отрицательно, эта зараза уже проникла в Debian, где уже многие пакеты неоправданно тянут python зависимостью. (mplayer например)


    1. semibiotic
      31.03.2022 01:11

      И это без учета кривой совместимости python между версиями, и его человеконенавистнического дизайна.


  1. Am6er
    28.03.2022 20:24
    +23

    Пожалуйста, прекратите писать приложения. Ведь чем сложней код - тем больше ошибок можно в нём допустить. Ну, такой себе заголовок у статьи. Вы поняли.

    А вот за сам контет - спасибо. Ещё добавили бы про trap-ы, раз затронули этут тему..


    1. siziyman
      28.03.2022 22:07
      +1

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


    1. 0xd34df00d
      29.03.2022 01:52
      +5

      Пожалуйста, прекратите писать приложения. Ведь чем сложней код — тем больше ошибок можно в нём допустить.

      Я больше всего радуюсь тем коммитам, где получается удалить побольше кода.


  1. bogolt
    28.03.2022 20:52
    +2

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


    1. mc2
      29.03.2022 03:50
      +1

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


  1. stitrace
    28.03.2022 20:58
    -1

    удалено


  1. ophil
    28.03.2022 21:28
    +5

    это совет конечно автору, а не переводчику. Вместо кучи плохих примеров дать несколько хороших. Использую в скриптах 2 принципа: streamlining и shortcircuting, посм. хорошие примеры всегда можно в /etc, раньше был специальный skeleton. Первый принцип означает вынести все проверки в начало скрипта, напр.:
    #! /bin/bash -x
    test $# -eq 2 || {
    echo -ne "\a\n
    usage: $0 month year\n
    calculates number of days in the month\n
    "
    exit 1
    }
    test $1 -gt 0 -a $1 -lt 13 -a $2 -gt 0 || {
    echo incorrect value of month or year
    exit 1
    }
    echo $(cal $1 $2)|sed 's/^.* //'
    shortcircuting - вместо кучи if/else использовать || или && и сразу выходить с сообщением.


    1. acmnu
      28.03.2022 22:01

      shortcircuting - вместо кучи if/else использовать || или && и сразу выходить с сообщением.

      Надеюсь вы вкурсе, что if/then/else и a && b || c не эквивалентны?


      1. thatsme
        29.03.2022 10:04
        +2

        Он, я думаю, в курсе. Тем удивительнее видеть в статье рекомендующей не использовать bash, рукожопый скриптинг без проверок.
        Примеры на самом деле плохие. Это примеры уровня 2-й день изучения sh.


    1. mc2
      29.03.2022 03:52

      А первую проверку бы сократить: если не два, сообщение и выход.


  1. Alexey2005
    28.03.2022 21:40
    +15

    Самый лютый ад в sh — это экранирование и работа с переменными (в частности, их подстановка). Как ни старайся, а всё равно попадётся такое имя файла, которое завалит вам весь скрипт. Такое ощущение, что оно специально задумано так, чтобы там в принципе не существовало хоть сколько-то надёжного способа заэкранировать и подставить.


    1. legolegs
      29.03.2022 00:10

      Как ни старайся, а всё равно попадётся такое имя файла, которое завалит вам весь скрипт.

      Ну это не правда. На практике защититься от всего, кроме \n тривиально, а для 100% непробиваемости придётся понадобавлять -0 и т.п.

      Вложенные кавычки бывают замороченными, ну так не надо их использовать. Особенно не надо использовать eval и эквивалентные конструкции. Если сложно - надо переписать просто, средства есть.


      1. AnthonyMikh
        29.03.2022 14:49
        +1

        Если сложно — надо переписать просто

        … на какой-то другой язык, который не будет по умолчанию вставлять палки в колёса


        1. thatsme
          29.03.2022 17:58

          Можно просто за правило взять, использовать вместо обратных кавычек $() и eval, а всё что подаётся как аргумент брать в двойные (там где требуется получить значения переменных) или одинарные (когда эвалуация содержимого не требуется до передачи аргумента) кавычки.

          С именами файлов ... сейчас во времена UTF-8, выдумщиков хватает. Поэтому всё что содержит имена файлов, ни в коем случае в eval сотоварищи подавать нельзя. Только использовать как аргументы и всегда как минимум в двойных кавычках.
          Когда кажется что "фсё, это точно невозможно сделать!", вспомните про awk, и сгенерируйте баш скрипт им. И проверок на имена файлов можно набубенить мама не горюй ... Одна проблема: осознанно читать это смогут единицы.

          Отсюда вывод: если требуется реюзабл код, который необходимо мантейнить, то:

          1. Не пишите его на баш или

          2. Не генерируйте его.

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


    1. Soukhinov
      29.03.2022 21:23

      Самый лютый ад — когда sh используется не для разработки и деплоя, а в продакшене, как часть серверного приложения, например. Тогда «завал скрипта» будет означать уязвимость и потенциальный взлом системы.


  1. Tzimie
    28.03.2022 21:46
    +3

    То ли дело CMD.exe (шутка)


    1. andrey_ssh
      29.03.2022 08:29

      Для кого шутка ...

      а для кого способ создать серверное ПО промышленного назначения.


      1. blind_oracle
        29.03.2022 10:47

        Некоторые и всю бизнес-логику в SQL реализуют, но это не значит что это правильный подход...


        1. kolu4iy
          29.03.2022 13:34
          +1

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


    1. sshikov
      29.03.2022 20:00
      +1

      Шутки шутками, а имеющийся в windows в качестве поддержки скриптовых языков WSH, в некоторой степени намного удобнее баша. А кто продолжает на cmd — ССЗБ.


  1. Kirikekeks
    28.03.2022 21:58
    +1

    set -o nounset

    set -o errexit

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


  1. acmnu
    28.03.2022 22:12
    +8

    По сравнению с sh, python слишком низкоуровневый. Т.е. код, эквивалентный вот такому:

     zcat /var/log/nginx/access.log.2.gz | awk '$6 == "\"GET" {print $1}' | sort -un

    Займет довольно много строчек, даже применяя все батарейки.

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


    1. unsignedchar
      28.03.2022 23:11
      +2

      Python просто не для однострочников. Если в приведенной конструкции появится что-то более сложное чем plain text (xml или html, например) - на bash это станет просто ужасно. На Python - +несколько строчек.


      1. acmnu
        29.03.2022 00:22

        В целом согласен, но это скорее потому, что юникс сделан под плеин текст. Если бы это было не так, то баш был бы другим. Думаю он был бы похож на powershell.

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


        1. mc2
          29.03.2022 03:57

          Почему был, и есть;)


      1. git-merge
        29.03.2022 09:23
        +1

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

        Написать скрипт на bash/sh - означает высокую вероятность того, что через 10 лет он будет работать.

        Написать скрипт на python - означает высокую вероятность, что через 10 лет в условиях тотальной смены Python3 -> Python4 его придётся выбросить.

        да и тотальная смена Python2->Python3 уже идёт несколько лет, а конца пока не видно


        1. worldmind
          29.03.2022 09:33
          +2

          Когда питон последний раз ломал обратную совместимость?


          1. git-merge
            29.03.2022 10:15
            -1

            у т.н. "батареек" это происходит постоянно

            а по самому Python - до сих пор ведётся работа по миграции дистрибутивов с 2.7 на 3.

            к тому моменту, как она будет закончена, начнётся миграция с 3.X на 4.

            • bash скрипты написанные 10 лет назад преимущественно работают нормально

            • python-системные скрипты за 10 лет, преимущественно переписаны из за обратной несовместимости

            Какой-либо язык можно предлагать на замену bash, но точно не python. У Python слишком подмоченная репутация


            1. worldmind
              29.03.2022 10:55
              +2

              Не могу сказать что вы ответили на мой вопрос.

              Какие ещё дистрибутивы ведут миграцию? Даже в дебиан давно третий питон по дефолту.


              1. andrey_ssh
                29.03.2022 11:34
                +3

                В Debian 11

                >python

                ... команда не найдена

                python 3 запускается командой

                >python3

                в Debian 9

                >python

                вызывает python 2.7

                Таким образом, есть возможность, что скрипт на питоне сломается ещё не запустившись.


                1. unsignedchar
                  29.03.2022 12:56

                  Есть специальный костыль ;) python-is-python2 и python-is-python3.


        1. siziyman
          29.03.2022 15:08

          Написать скрипт на bash/sh - означает высокую вероятность того, что через 10 лет он будет работать.

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


        1. vvzvlad
          29.03.2022 18:14

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

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


    1. RiaD
      29.03.2022 09:10
      +1

      from plumbum.cmd import zcat, awk, sort
      (zcat['/var/log/nginx/access.log.2.gz'] | awk ['$6 == ""GET" {print $1}'] | sort ['-u', '-n']) & FG

      как-то так. И уже пробелы в названия файла не страшны..


      1. acmnu
        30.03.2022 10:10

        Да, про эту штуку я уже читал, но меня она немного подбешивает. Мне не нравится, что в python втащили синтаксис bash, перегрузив привычные операторы (если я правильно понял как оно устроено).

        Т.е. синтаксис как бы разваливается на две части: обычный питон, и питон с магией.

        Я бы предпочел более привычную для python историю. Что-то типа

        zcat("access.log.2.gz").awk('$6 == "\"GET" {print $1}').sort(uniq=true, number=true)

        И да, такое можно сделать и даже вроде как не сложно, но сила привычки велика.


  1. F0iL
    28.03.2022 23:38
    +3

    Как писать bash-скрипты надежно и безопасно: минимальный шаблон. Не панацея, но мне в свое время оказалось весьма полезным.


    1. Goupil
      29.03.2022 00:10

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


  1. JPEGEC
    29.03.2022 02:02
    +2

    Если заглянуть практически в любой шелл скрипт системы, то мы увидим многочисленные проверки существования, файлов, каталогов, программ. Снимающие практически все озвученные автором "проблемы".

    Не верю что автор об этом не знает.

    PS Как язык bash конечно так себе. Но это совершенно не повод везде и всюду пытаться совать python. Всякому овощу свое время и место.


  1. edo1h
    29.03.2022 02:07

    Проблема №1: Ошибки не останавливают выполнение

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


    1. Soukhinov
      29.03.2022 21:26

      Падение — это уже хоть какая-то обработка.


  1. ckpunT
    29.03.2022 06:29

    Название статьи должно содержать слова: "Пожалуйста" "пишите" "shell-скрипты" "правильно"


  1. vasilisc
    29.03.2022 06:59
    +6

    Извините меня админа, но топорные bash скрипты автоматизации работают годами. Нет ни времени ни желания при выходе новой версии того же Питона переписывать код вчера с 2 на 3 версию, а завтра с 3 на 4 и т.д.


    1. Physmatik
      29.03.2022 22:34

      А глобальные версии питона живут десятилетиями, а не годами. И кто тогда, спрашивается, имеет преимущество?


      1. vasilisc
        30.03.2022 09:10

        bash =)

        У меня есть скрипты с 2004 года, а это уже почти 20 лет и там не нужен Питон от слова совсем. Bash - это прежде всего простота и быстрая возможность изменить/добавить. В консоли сервера в редакторах вы ещё настраиваете замену tab на череду пробелов, чтобы не сломать python скрипт (возможно не ваш), а я уже поправил свой bash скрипт и он снова в строю.


        1. unsignedchar
          30.03.2022 09:16

          Так себе практика - править чего то исполняемого в консоли сервера. Python/bash - неважно.


          1. vasilisc
            30.03.2022 10:47

            Не во всех конторах серверов столько что обязательны инструменты оркестровки типа ansible, а перед этим Git, череда тестов и CI/CD. Много админов используют лишь SSH и работу на сервере при проблемах или в случаях каких-либо изменений.


  1. N-Cube
    29.03.2022 07:14
    -1

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


  1. Vitaly83vvp
    29.03.2022 07:42

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


  1. aml
    29.03.2022 08:26
    +1

    В общем суть статьи: обработка ошибок в шелле по умолчанию не подходит для задач типа CI/CD (где действительно обычно лучше упасть, если хоть что-то пошло не так), и не забудьте делать вот так.


  1. vaniacer
    29.03.2022 08:32
    -1

    Вы меня убедили, ухожу на покой и удаляю все свои репы. Клонируйте пока github не прикрыли можно: piu-piu, sshto, kube-dialog


    1. Ritan
      29.03.2022 12:56

      На brainfuck тоже можно написай gui. Но нужно ли?


  1. myx_ostankin
    29.03.2022 09:11
    -1

    Простите, а что мешает использовать ShellCheck, который ловит вроде бы абсолютно все вышеперечисленные проблемы?


  1. deadfish45
    29.03.2022 09:20
    +4

    Мне кажется что если кодер допускает вот такую ошибку

    cp newfil newfile2  # Deliberate typo
    echo "Success"

    то python не поможет. Здесь человек явно хочет вывести "Успех", вне зависимости от результата оперции. С операцией копированния, казалось бы очень простой, много что может пойти не так, на некоторые проблемы питон может выдать эксепшин, а на некоторые нет. Так у пользователя может не быть доступа на чтение к файлу источнику или на запись к фйалу/директории назначения, на носителе может закончиться место, может закончиться виртаульная память, и т.д. Все эти ошибки ловятся и питоном и простой проверкой успешности выполнения команды cp, например через переменную "$?" . Но если newfile2 уже существует и является директорией, то файл скопируется в директорию, cp завершится успехом, вряд ли это тот результат который ожидал автор скрипта. Если файл источник уже сещуствует и является пайпом, тогда touch не возымеет эффекта, а копированние зависнет на блокующем read, и выполнение копированния не закончится пока процессу не придёт сигнал, или пайп не откроет на чтение и закроет, другой процесс. У sh есть свои преимущества, сильные стороны из-за которых им и пользуются, его даже необходимым злом не назвать, если ты порезал руку ножом это повод задумываться о том, чтобы перестать им пользоваться? Статью с таким же успехом можно было назвать "Пожалуйста, прекратите писать какие бы то ни было скрипты или программы, если вы не являетесь экспертом, с нечеловеческой способностью держать все факторы в голове и не допускать ошибок".


    1. Physmatik
      29.03.2022 22:41

      Подставьте вместо echo "Success" любую операцию с newfile2.


      Приведённый пример — именно что пример, единственная задача которого состоит в максимально лаконичной демонстрации проблемы.


      1. edo1h
        30.03.2022 01:23

        Подставьте вместо echo "Success" любую операцию с newfile2

        ну вот когда подставится, тогда и надо делать обработку ошибок. что в баше, что в питоне.
        и падение с нечитаемым stack trace — это не сильная сторона питона, а его проклятие. постоянно сталкиваюсь прогоняя скрипты ansible, например.


        P. S. вот реальный init с одного проекта:


        #!/bin/sh
        
        /usr/sbin/nft -f /etc/nftables.conf
        /sbin/sysctl -p /etc/sysctl.conf
        
        exec /usr/bin/runsvdir /sv

        где мне тут нужно падение скрипта в случае ошибок и зачем?
        и зачем мне в проекте, где образ системы пять мегабайт (ну ладно, 10 с ядром и бутлоадером), в разы более жирный питон?


        1. Physmatik
          30.03.2022 02:34

          А вы совет "часто мойте руки" тоже так воспринимаете, моя руки буквально после каждого чиха? Или всё же понимаете, что советы дают без приписки "делать во 100% случаев во всех возможных существующих юзкейсах", ибо каждый случай уникален и люди тем и отличаются от машин, что умеют глубоко понимать контекст?


          Если у вас система 5МБ, то, разумеется, никаких питонов тащить не надо. Но система в 5МБ — это, мягко говоря, не совсем частый случай. Обычно люди пишут баш-скрипты под самые обычные дистрибутивы типа дебианов и сентосов, где питон уже есть. Да и "питон" пишут чисто для примера, можете брать перл или TCL. Смысл статьи в том, что:


          1. Падение при первой ошибке (и другие ключи) гораздо чаще сохранят вам нервы, чем нет. Разумеется вы, как разумный и понимающий контекст человек, можете эти ключи не использовать, если они будут противоречить логике скрипта.
          2. Использование более строгого скриптового языка общего назначения (НАПРИМЕР, питона) в среднем сильно облегчит как дебаг, так и последующее чтение вашего кода, который на баше слишком часто write-only.

          Но вы влазите со своим очевидно нестандартным юзкейсом в виде системы на 5МБ и начинаете доказывать, что вся статья — хрень, ибо вам конкретно не подходит. Неужто сами не понимаете, в чём проблема?


          1. edo1h
            30.03.2022 10:53

            Но вы влазите со своим очевидно нестандартным юзкейсом

            я влезаю со стандартным use case: скрипт на несколько строчек, в котором обработка ошибок или не нужна, или нужна в паре мест.


            с тем, что если скрипт не помещается на экране, то нужно плотно подумать над тем, чтобы переписать его с sh/bash на другой язык, я полностью согласен, но в статье было написано совсем не это.


            1. vvzvlad
              30.03.2022 11:04

              я влезаю со стандартным use case: скрипт на несколько строчек, в котором обработка ошибок или не нужна, или нужна в паре мест.

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


  1. mawinist
    29.03.2022 09:22
    +1

    bash или тем более sh есть в любом окружение unix подобном... В любом самом урезанном контейнере, а вот тот же питон нет. К тому же как по мне сравнивать python и более старый и простой bash явно не стоит, питон естесно выграет по функциональности т.к. является ЯП общего назначения в отличии от баш.


  1. itded
    29.03.2022 09:23
    +2

    Пока shell и powershell скриптов мне хватало за глазы, отладка и тестирование вам в помощь. Хотелось бы перейти на python или go, курсы по которым я прошёл, но когда смотришь на задачу и понимаешь, что ее можно решить sed и awk, сложно заставить себя вспоминать типы переменных и функции


  1. worldmind
    29.03.2022 09:31

    Да, как раз такой статьи не хватало когда я писал заметку про питонную замену шелла - xonsh


  1. 13werwolf13
    29.03.2022 10:20
    +2

    у автора видимо почасовая оплата, ну или оплата по кол-ву символов.

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

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


    1. siziyman
      29.03.2022 13:15
      +1

      на питоне пишется дольше и выходит толше, а результат один

      если же всё оттестировать и по сто раз перепроверить

      Так в том и дело, что большинству разработчиков сделать это с питоном/груви будет проще, чем с башем.


      1. 13werwolf13
        29.03.2022 13:16
        -1

        ни разу не видел в работе (на личных тачках не считается) у разработчиков shell скрипты. только у админов и девопсов


        1. siziyman
          29.03.2022 13:19

          Не у всех и не всегда выделенные девопсы под каждый чих есть, во-первых.

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


  1. merlin-vrn
    29.03.2022 10:35
    +3

    А почему вместо Bash — Python, а не TCL? Уж не потому ли, что автор про него не знает? Озвученных "недостатков" bash у него нет. Главное, это Tool Command Language — он специально предназначен для таких задач, которые часто решают Bash-скриптам

    Гораздо легче пайтона, перла - взамен баша годится получше. Достаточно компактный и переносимый, легко писать для него собственные расширения (если уж приспичит). Именно он встроен в Cisco IOS для кастомной автоматизации. Событийная модель и работа с сокетами у него эталонная, если кто-то думает что это в [подставь свой любимый язык] изобрели, вы скорее всего ошибаетесь. Есть и shell, причём он работает вместе с событийным приложением, например, оконным (Tk) — можно при отладке в процессе работы приложения в шелле менять значения переменных, заменять обработчики и т. п. Кстати, Tk, SQLite и некоторые другие проекты изначально задумывались именно как дополнения к TCL, а уж потом обрели (относительно) самостоятельную жизнь.


    1. acmnu
      30.03.2022 10:00

      Ну в мире много интересных систем, которые не стали распространнеными. И не смотря на то, что TCL хорош, но по распространенности до bash и python ему невероятно далеко.


  1. ggo
    29.03.2022 10:54

    одна из альтернатив — skarnet.org/software/execline


  1. worldmind
    29.03.2022 11:56

    Погугул на эту тему тут человек целую книгу про это упоминает.


  1. johnfound
    29.03.2022 15:11
    +2

    Все говорят, что bash нельзя,
    Все говорят, что bash нельзя,
    Все говорят, что bash нельзя,
    А я говорю, что буду.


  1. asnow
    29.03.2022 15:58

    Из разряда не делай это потому что ты не умеешь это готовить. sh хороший инструмент, с чего это вдруг его нельзя использовать. Ок то что вы только python знаете это как бы не проблема сообщества. Да sh имеет много нюансов, я и сам не супер хорошо его знаю. Но для этого и есть манулы и учебники. Если в задаче у тебя есть требования только на unix среду то тут сразу вопрос встанет ли ваш python или ещё какая приблуда в физических ограничениях заказчика


  1. wilelf
    29.03.2022 18:06

    Сначала почудилось, что CGA...


  1. Alexander_The_Great
    29.03.2022 19:30

    >на скомпилированном языке код даже не компилировался бы.

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


  1. ALiEN175
    30.03.2022 00:44
    +1

    Бред какой-то. Пожалуйста, прекратите писать shell-скрипты — а итоге и в shell не сумели. Точно такой же говнокод я могу и на питоне, и на ждаве и на го организовать. У автора какая-то личная неприязнь к языку командного интерпретатора. И вот прям капсом — SHELL НЕ ЯЗЫК ПРОГРАММИРОВАНИЯ!

    Проблема №1: Ошибки не останавливают выполнение
    А кто вам сказал, что ошибки ДОЛЖНЫ останавливать весь скрипт? Одна строка — одна команда.

    Проблема №2: Неизвестные переменные не вызывают ошибок
    Что написали, что и получили. Нет $PTH — получите — "".

    Проблема №3: Пайпы не отлавливают ошибки
    см. Проблема №1

    Проблема №4: Subshells работают странно
    Работают так, как должны. Можете считать контейнером, если так понятнее.


  1. garbagecollected
    30.03.2022 03:29

    Про grep, rsync уже сказали. Про git, diff, dd пока молчали.

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

    mkdir -p ./{00..99}/{00..99}/{00..99}

    Как такое сделаете на пресловутом python? - Сами используйте свои циклы наздоровье!

    Или, например, если мне нужно скачать 1000 файлов при разрешенных сервером 10 одновременных соединений:

    cat ./urls.txt | xargs -L1 -P10 wget

    Как вы будете реализовывать такую очередь на python? - Какую именно реализацию Queue выбрать?

    Вдруг мне захотелось псевдо UI:

    dialog --yesno "Are you sure?" 5 17

    Что там есть у python? ncurses и blessed? - Нет уж, спасибо!

    Автор упоминает о shellcheck, а вот про существование morbig он не в курсе. Наверное, Роскомнадзор заблокировал Google.

    Ах, если б не этот bash, никто бы бед не знал. Прям камень предкновения. Да! Давайте заменим bash на python, systemd на docker, vim на PyCharm, электронную почту на Discord, ssh на TeamViewer, Google.Play на pacman из ArchLinux. И да настанет процветание! - Нет, не настанет.

    Лучше б действительно написали статью о том, как включить docker в сборку ядра, что бы ядро не выросло за пределы условных 50Мб при простеньком gzip-сжатии.

    P.S. я и сам не в восторге от bash, и везде заменяю его на fish. Но это же вкусовщина! А что делать любителям zsh на MacOS? Там нет этих проблем? Тоже переходить на docker?


    1. unsignedchar
      30.03.2022 07:38

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


      1. edo1h
        30.03.2022 10:56

        Правда, они напрочь синтетические

        странно, а у меня полно таких однострочников.


  1. iwram
    30.03.2022 08:12
    -1

    Знаю людей, которые до сих пор убеждены, что все новые технологии - это обёртка надо bash. Везде алгоритмы и последовательности, значит это баш. :)


  1. lumag
    30.03.2022 11:23
    +2

    Пожалуйста, прекратите писать скрипты на Bash. Это так себе идея. Чем дальше, тем чаще приходится писать на чистом (POSIX) sh. Минус bash-измы, зато возрастает переносимость скриптов. Различия, в целом, минимальны.


    1. edo1h
      30.03.2022 13:20
      +1

      что различия минимальны не соглашусь, где, например, hash-таблицы в posix shell.
      но у меня под рукой полно систем с busybox, докер-контейнеров без bash, так что за совет по возможности использовать posix shell я голосую обеими руками.
      ну а если возможностей posix shell недостаточно, то действительно стоит задуматься о переходе на «настоящий» ЯВУ


  1. freecoder_xx
    30.03.2022 21:34

    Для Rust кроме xshell, есть еще rust-script.


  1. Lapk
    31.03.2022 09:58
    +2

    дальше первой проблемы читать не стала. Автору надо курить маны на баш.


  1. Burunduk73
    31.03.2022 13:09

    Пожалуйста, прекратите втюхивать недоязык питон!

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