Pull to refresh

Comments 251

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UFO just landed and posted this here

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

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

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

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

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

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

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

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

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

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

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

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

Если на перле писать без извращений, то он ничем не хуже питона, просто уже не модно/молодежно

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

я видел, правда, там скрипты такого размера и сложности, что и shell-скрипты не вызвали бы проблем с восприятием

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

Смысл этой штуковины: работать в хуке пакетного менеджера. Активная работа с файлами и разделами, вычленение 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

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

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

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

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

интерпретируемые языки

А bash какой, простите, язык?

Это CLI shell. Оно хоть и натягивается на понятие языка, но весь обрабатывающий код в одном файле, без зависимостей. Отсутствует стандартная библиотека, и многое другое, что характерно для зрелых языков — несмотря на то, что shell давно является зрелым.
Хотя даже несмотря на это, pacman иногда умудряется выдернуть его из-под себя… но эта проблема так же не имеет ничего общего с обновлением модулей интерпретируемого языка.

Ну такой аргумент. Решается мультистейджем на уровне локальной сборки и кешем на уровне пайплайна.

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

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

Go. Серьёзно!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вот этап с rsync как раз и можно оставить вовне:

python generate.py && rsync <...>

Или даже exec'нуться из питона, если уж так хочется.

В Go это делается как и везде - либо через использование написанной на Go или C библиотеки (их навалом), либо через вызов нужной утилиты через Exec.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Оно честное в рамках задачи «скачать файл».
Но впрочем, я нашел для питона wget, который делает тоже самое в две строки:
import wget
filename = wget.download('https://github.com/foo/bar.txt', "/etc/") 

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

Незаменимости никакой нет. Просто подумай, как долго ты будешь на 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 будешь доставлять до места применения?

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

Этот код пишется в те же несколько строк (у меня ушло вот 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», то да, питон не нужен. Если задача чуть сложнее и включает в себя еще десяток команд — то питон внезапно оказывается более сопровождаемым и менее склонным к сюрпризам.

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

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

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

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

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

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

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

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

А, э, простите, я разве утверждал обратное?
Если питона нет, то на питоне писать не получится. Если в системе только 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 и так далее. Заменить — можно.

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

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


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


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

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

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

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

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

import gzip, hashlib

И ещё по пару строчек на каждое пожелание. Не бином Ньютона.

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

Ну мы же про баш говорим, а он (его пайпы) из коробки многопоточные. wget | tee >(sha1sum ...) | gunzip > file будет работать на трёх ядрах со скоростью самой медленной операции (сети, подсчёта хеша, разжатия или диска).

Если бы у нас был не один архив, а N, то у нас есть кондовый xargs --max-procs=$(nproc) или замечательный, но новомодный, переусложнённый и перловый GNU parallel. И это меньше одной строчки.

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

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

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


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

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

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

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

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

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


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

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

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

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

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


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

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


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

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

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

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

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

Кстати, не только.
Вот что говорит справка:
Взять дату из файла, загрузить если удаленный файл новее:
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

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

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

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


А вот вопросами формата и типичного использования во время написания кода вам придется задаться и при подключении 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.

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

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


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

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

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

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

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

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

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

где сейчас используются 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 инстанса?

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

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

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

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

Я обрабатываю логи в 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

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

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

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

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

Ничего не писать, все продать, уехать в лес навсегда и почувствовать, что всё сделал правильно. Надеюсь, что автор статьи так и поступит.

ну да, статья высосана из пальца. Задачи бывают разные, есть масса случаев, когда достаточно шела и нескольких консольных утилит. Первый пример в статье вообще странный, 'cp newfil newfile2 & echo Success' и всё

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

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

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

shell, python, ocaml, idris

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

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

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

UFO just landed and posted this here

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

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

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

set -Eeuo pipefail

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Однако давайте только посмотрим на примеры ошибок из статьи, по которым предлагается на каждую 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. Но в то же время он будет называть себя программистом.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Вам же в статье написали, что в 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])
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Внедрять coding guides

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

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

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

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

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

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

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

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

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

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

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

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

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

это совет конечно автору, а не переводчику. Вместо кучи плохих примеров дать несколько хороших. Использую в скриптах 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 использовать || или && и сразу выходить с сообщением.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

set -o nounset

set -o errexit

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В Debian 11

>python

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

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

>python3

в Debian 9

>python

вызывает python 2.7

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

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

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

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

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

Баш любит ломаться просто при смене окружения. Завтра захотите перейти на другой дистрибутив, и все, ищите где у вас в какой утилите ключ теперь по-другому вызывается.
from plumbum.cmd import zcat, awk, sort
(zcat['/var/log/nginx/access.log.2.gz'] | awk ['$6 == ""GET" {print $1}'] | sort ['-u', '-n']) & FG

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

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

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

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

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

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

UFO just landed and posted this here

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

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

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

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

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

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

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

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

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

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

bash =)

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

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

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

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

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

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

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

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

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

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

cp newfil newfile2  # Deliberate typo
echo "Success"

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

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


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

Подставьте вместо 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 с ядром и бутлоадером), в разы более жирный питон?

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


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


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

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

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

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


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

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

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

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

Представьте себе ситуацию, вы законнектились к рабочему компьютеру из командировки через два промежуточных ssh-сервера. Набрали длиннейшую команду по поиску и архивации чего-нибудь. И тут случайно поставили лишнюю скобочку. Жмете Enter, и bash отваливается, окошко терминала закрывается, и вы уже смотрите на рабочий стол.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Как бы потом, смотря на скрипт в 10к строк не пришлось за стаканы браться.

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

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

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

Очередной кривой low-effort перевод на Хабре, не обращайте внимания.

UFO just landed and posted this here

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

Некоторые программы могут падать или отдавать коды завершения отличные от нуля, и это норма. Например, при перенаправлении ввода-вывода программы в пайп она может заявить, что у нее unexpected end of file — но кому какая разница, если работу она сделала? Whiptail отдает 255 код в случае выбора кнопки Cancel.
В некоторых случаях обрабатываются вообще потоки, и в нем может прилететь что-то невалидное — что теперь, скрипту падать посреди цикла for вместо перезапуска пайпа?

Как то вы перескакиваете с "Проблема №1: Ошибки не останавливают выполнение" на термины "код возврата". Очевидно, что если кто-то нормально возвращает 255 код, то скриптописатель должен это знать, учитывать и не считать ошибкой.
Но если на 3 стоке скрипта из 50 строк произошла ошибка — то остальные 47 в помойку в 99% случаев.
А 1% это когда ошибка в выводе отладочного/статусного сообщения, а не в бизнес логике.


В некоторых случаях обрабатываются вообще потоки, и в нем может прилететь что-то невалидное

Да, например посреди скачиваемого архива прилетает HTTP 500 текстом, давайте его минут 5 попишем в содержимое архива, потом снова будет лететь архив, мы его аккуратно докачаем и итоговую каку будем считать бэкапом. Зачем нам прерываться и обрабатывать ошибку?
(вспоминая менеджеры докачки диалап вермен)

Как то вы перескакиваете

Кажется, вы давно не брали в руки shell. Возможно даже никогда.
Для реализации конструкции переход к следующей команде в случае успеха используются коды возврата: то, что падает в $?. Коды возврата интерпретируются при этом схожим образом, как байт сокращается в true/false. Только успешным считается только один код: 0.
Если бы вы когда-нибудь использовали set -e или объединяли команды в цепочку операторами &&/|| — вы бы это знали.


Но если на 3 стоке скрипта из 50 строк произошла ошибка

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


Да, например посреди скачиваемого архива прилетает HTTP 500 текстом

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


Что наглядно иллюстрирует: у хорошего мастера виноваты плохие инструменты, у плохого — свои.

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


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


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


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

И снова иррелевантность во все поля.
Ни sql, ни pcl, ни less, ни g-code, ни shell не являются языками программирования.
В shell вся специфичная логика реализована в командах и задается ключами.
И вот это:


Ошибка должна быть или обработана

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


выбрали оптимистичный паттерн

Не оптимистичный а рациональный. В shell-скрипте не может произойти деления на 0, разыменования нулевого указателя, и прочего низкоуровнего.
А если у вас не хватает инодов, закончилось место, недостаточно памяти — то это не проблема shell, это глубоко системная проблема. Устранится эта системная проблема — перестанет падать и shell script. Хотите предусмотреть это? Обмазывайтесь проверками так же, как сделали бы в программе на Си, неэффективно дублируя проверки уже имеющиеся в вызываемых программах.


Но потом приходят чудаки с возгласом: я вижу if — сейчас я вам запрограммирую! — и, как обломаются, так начинают искать виноватую скамейку.


А если сообщение об ошибке

ВНЕЗАПНО, скрипт это просто последовательность shell команд. И они, таки, выдают сообщения об ошибках. Нет связи с докер-демоном, нет места, нет доступа, файл не найден, другие — но ровно те же самые, что и обычная программа, потому что их и выводит обычная программа.


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

Не, деление на 0 произойти почти не может. (хотя если поделить на количество файлов попавших в фильтр...) Зато может неправильно вычислиться динамический путь, или текущий каталог оказаться внезапно c:\win\system32, потом у нас произойдет ошибка при cd .\temp на которую мы забьем, а потом мы удалим все содержимое текущего (мы же уверены что это temp) каталога и запишем туда новые временные данные.
Вы все еще утверждаете, что если ошибка произошла, то безопасно на это забить и продолжить выполнение?

Выходит, все-таки, неосилили, но мнение имеете.


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

Вы все еще утверждаете, что если ошибка произошла, то безопасно на это забить и продолжить выполнение?

давайте по порядку:


  • зачастую именно в шелл-скриптах игнорирование ошибки — оптимальный подход (я приводил пример с init-скриптом, ну не смогли мы, например, применить правила для nftables, что теперь, хост должен совсем не загрузиться?!?);
  • такое поведение заложено в стандарты posix shell, поэтому никто не будет менять дефолты;
  • но механизмы обработки ошибок в shell-скриптах есть (включая описанный в статье set -e).

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

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

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


небольшой оффтопик: помню сколько нервов мне съело внезапное открытие флага nofail в fstab у systemd.
посыпались бэды на диске с данными, хостер его поменял, система (установленная на совсем другой накопитель) не грузится.
если во времена до systemd ошибка монтирования была бы просто проигнорирована, то теперь без явного указания nofail для точки монтирования загрузка системы остановилась на этом месте (и да, это ещё до запуска sshd).
благо у этого хостера можно заказать kvm (услуга не моментальная, но через несколько часов проблема была таки решена). у некоторых же хостеров в подобной ситуации или передавать саппорту рутовый пароль и надеяться, что смогут разобраться, или заказывать переустановку операционной системы.


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

так нет же. он не назвал статью «почему я (разработчик на питоне) перестал использовать bash для деплоя питоновских приложений», он почему-то хочет запретить нам использовать shell для решения наших задач.


P. S. статья-то на самом деле вполне годная: способов выстрелить себе в ногу при использовании shell достаточно, и парадигма «ошибка по умолчанию игнорируется» может быть непривычна, и даже с тем, что для более-менее сложных задач shell не стоит выбирать, я вполне согласен.
но вот с тезисом «в Европе теперь никто на пианино не играет, играют на электричестве» «выкиньте баш и пишите всё на питоне» я согласиться не могу.

помню сколько нервов мне съело внезапное открытие флага nofail в fstab у systemd

А оно разве systemd? Вроде как noauto,nobootwait,nofail это сам fstab, опции systemd начинаются с x-systemd.
Но это как раз из тех случаев, для которых придумали dropbear и прочий ранный ssh.

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

в условиях "выстрелил и забыл" поведение "полный стоп"

Спасибо, вы сделали этот день!
Это ж взаимоисключающие параграфы: или fire-and-forget или контроль и анализ статуса.

Про 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?

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

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

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

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

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

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

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

А вам бы потренировать выдержку :)

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

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

Вопрос не в том, что не хватает возможностей. Вопрос в том, что с точки зрения полноценного языка программирования он еще кривее, чем питон.

Язык управления командами такой же "язык программирования", как less или pcl.

Ну в принципе jshell тоже есть :) И я не думаю, что это намного сложнее питона.

Вот например возможность скачать первую строчку каждого всех release notes питона с обработкой ошибок.

import java.io.*;
import java.net.URL;
import java.util.regex.*;
var pattern = Pattern.compile("href=\"(.*?)\"");
try (var br = new BufferedReader(new InputStreamReader(new URL("https://www.python.org/downloads/").openStream()))) {
  br.lines()
    .filter(x -> x.contains("Release Notes"))
    .map(pattern::matcher)
    .filter(Matcher::find)
    .map(m ->m.group(1))
    .forEach(u -> {
      try(var rbr = new BufferedReader(new InputStreamReader(new URL(u).openStream()))) {
        System.out.println(u + ": " + rbr.lines().findFirst().get());
      } catch(Exception e) {
        System.err.println("Cannot open " + u);
      }
    });
} catch(Exception e) {
  System.err.println("Cannot open pPython dowlnoads. Try Java");
}

А мужики-то и не знали!!

Дно в очередной раз пробито. Автор, про trap слушали? А про то что значения переменных в принципе проверять надо? Научитесь писать для начала на BASH!

Весь мир (включая меня) что только не пишет и всё работает.Бедный Linux. Половина системы построена на скриптах, а оказывается так нельзя. Автор, не мелочись, давай ещё конфиги строго на JSON требуй).

Вывод простой - автор не знает о чем пишет, но активно пропагандирует свои курсы. КГ/АМ

весь перечисленный список "ошибок" - это слово-в-слово про джаваскрипт в браузере! :)

Весь перечисленный список причин - "почему же все используют этот ужасный язык программирования?" (сарказм) - это снова про джаваскрипт в браузере :)

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

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

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

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

И с этим флагом (-u) мы получим ошибку на следующем шаге (проверки на пустоту)

В каких-то вы сферических компаниях работаете. Каждый пишет на чем умеет лучше всего. Отличный совет пишите на питон, когда его вообще в компании из 100 человек не знает никто. Этот совет ещё более отличный, когда на баше пишешь конфиг деплоя в Дженкинс. А чё у нас не серкл-си-ай или ещё что модное? А потому что я (и многие из нас) пришли поработать 2-3 года, вдарить оффером по столу и получить ещё больше франклинов-per-month, скорее всего уже в другой компании, а не переделывать устои везде где приходишь.

Покрывать тестами скрипт такая себе затея. Если сейчас у вас на работе есть скрипт без тестов и комментов из сотни строк вам скажут так "его писал маэстро кода name, он уволился прямо перед тобой и уехал в Тибет без средств связи, вот инструкция как использовать, ничего там не трогай, а то взорвется". То будь он с тестами, у него будут и какие-то версии и документация - в итоге на вас прилетит задача-доработка и вам придется ковырять мерзкий язык. Оно вам надо?

echo "Коллега - круворукий ламер" && echo "Success"

Конечно, не стоит остро реагировать на статью и возводить её посыл в абсолют. Но разве заголовок "Пожалуйста, прекратите писать shell-скрипты" не является возведением в абсолют и очевидным кликбейтом на холивары в комментариях? Опытный специалист сам способен разобраться, когда и на чём ему писать. Без советов из интернета.

Shell (Bash/Dash/etc) есть практически в каждом утюге. А есть ли Python в этих утюгах или его надо предварительно доустановить?

UFO just landed and posted this here
Sign up to leave a comment.