Comments 73
Можно ли писать скрипты на C++
Конечно можно, но зачем? Мне думается, что правильнее подбирать инструмент под задачу, а не задачу под инструмент. Для скриптинга у C++ слишком многословный и сложный синтаксис, а также относительно бедная стандартная библиотека.
Для этой задачи гораздо лучше подходит Python и компилировать ничего не надо. Добавить shebang #!/usr/bin/env python3
и запускать прямо из bash. И все возможности stdlib (pathlib, sys, os, shutil, re, argparse, ...) к вашим услугам. А если чего-то не хватает, pip install --user ...
и погнали.
вроде std::vector, std::map или std::string т другие
std::filesystem (если компилятор позволяет)
конечно забавно скриптовать на C++, но в чем профит — непонятно. Скорости разработки оно не добавляет в сравнении с каким-нибудь питоном, пляски вокруг utf, стандартные контейнеры и filesystem крайне сомнительный профит. Быстрого подключения модулей нет ибо надо прокидывать все это дело компилятору. Предлагаю добавить хаб "ненормальное программирование"
Ну и речь шла, опять же, про скорость разработки, а не исполнения скрипта.
<сарказм>
Поверьте, очень быстро
Не видно даже подвижек чтобы хотя бы пакетные менеджеры использовали много потоков. Хотя бы для распаковки. И так во всем.
А что касается многопоточной работы базовых утилит, не забываем, что работают они с ПОТОКАМИ. То есть каждой скармливается поток, с которым она что-то должна сделать. Если нужно делать что-то в несколько потоков, просто запускаете эти несколько потоков и не маетесь с последовательным запуском. Я так и делаю, причём, во всё том же Bash.
Установку не надо, а скачивание и распаковку — да. Все что может быть безопасно распараллелено.
Можно добавить опцию. Но пока всех устраивает скачивание за 5 минут вместо 1 и распаковку 10 вместо 2.
P.S. На моей машине и на моей сети закачка идёт в два потока. Хотя и не всегда. Видимо, зависит от загруженности сети и отдающего сервера.
У xargs
есть отличный аргумент -P
, который как раз и запускает несколько процессов параллельно. Не многопоточность, но загрузить проц до упора вполне можно. Например, для кодирования больших объёмов текста с помощью Sentencepiece или преобразования FB2 ⇒ TXT.
Тогда уж можно сразу GNU Parallel вспомнить.
это что быстро работает?
Очень быстро, да. Но эксперимента ради предложил бы переписать утилиту yes из набора coreutils собственными силами и потом помериться в скорости. Там же голые Си под низом, которые отлаживали со второй половины прошлого века. В последней декаде решили еще бустануть, переписав на раст и сделав чуть более человечные API, кроссплатформенность и всякое разное прикольное (см. ripgrep, exa ). Но как упоминалось ниже — речь не про скорость работы, а про скорость разработки. Едва ли с быстренько можно написать какое-нибудь префиксное дерево чтобы вместо grep/sed использовать. Прототипировать может быть удобно, если из консоли не вылазить, но я обычно для такого использую sublime text с настроенной командой для REPL, а то и вовсе использую какой-нибудь wandbox.org или ideone.com. Так что это не та ситуация когда строгая типизация и манипулирование байтами имеет хоть сколько-нибудь значимое преимущество.
Второй нюанс — это все же не скриптование, а именно перекомпиляция, т.к. для полноценного скриптования нужен интерпретатор, вроде вышеупомянутого cling. Такое скриптование далеко не факт что работает сильно быстрее питона, если вообще быстрее.
И про второй момент: Python, если что, тоже компилирует скрипт, хотя результат сохраняет только для подключаемых модулей, так что это противопоставление не совсем корректно. Да, скорость компиляции у Python выше, но, опять же, помним про время разработки и время работы итоговой программы, потому что, как я уже написал, Python сохраняет результат компиляции только для подключаемых модулей, а инструмент автора — именно для основной программы. А библиотеки и так уже скомпилированы.
противопоставление не совсем корректно
Если называть питон чисто скриптовым языком — да, но я вроде только про скорость выполнения упоминал. Да и компиляция у питона ленивая. Можно было аналогично в противопоставление nodejs упомянуть. Тот еще медленнее да еще и по памяти прожорливее.
Что до sed/awk — для меня это обычно тоже темный лес. Т.к."сварщик я не настоящий" ибо пишу не с под vim в терминале и руки на мышку перекладываю иногда для моих нужд хватает find+grep с парой заученых ключей. Ну, и питон конечно же.
скриптовые интерпретаторы
ванильный луа, насколько мне известно, не компилируется как питон с артефактами (про новое издание не знаю наверняка). У перла и js/nodejs вроде та же история. Многие лиспы. Преобразование в байткод своей внутренней виртуальной машины не считаю.
день потерять, зато потом за пять минут долететь
в такой ситуации обычно имеет смысл завести cmake файл или еще какой-нибудь конфиг сборки, чтобы и в IDE можно было в случае чего кнопкой завести.
Разница в том, что питоноскрипт нормально кэширует сборку, а вот поделка автора статьи — кладет исполняемый файл в /tmp, что, конечно, при следующем запуске скрипта приведет к повторной компиляции. А как только автор втащит boost в свой "скрипт" на С++, то "привет, сосед!"
Чтобы сделать компиляцию безопасной там еще кучу вещей надо сделать — писать sha исходного файла программы, обеспечить конкурентность запуска "скрипта" (что будет если мы один "скрипт" очень быстро запустим два раза подряд), обеспечить пересборку бинаря при изменении кода скрипта и прочее-прочее. И, да, проверка по меткам времени — зашквар. Странно, что это все не продумано
но в чем профит — непонятно.Профит в повторном использовании
Вот сколько вам понадобиться написать и отладить такой скрипт на C++? С оптимизацией на уровне чтения файлов, многопоточностью, и другими специальными вещами?
Так что вот вам мой совет. Начните с малого. Освойте, для начала, например, однострочники на perl и обязательно регулярки. Тут, на хабре, были статьи по обоим темам. С перлом даже awk не понадобиться. grep, sort — там всего-то надо знать по паре-тройке ключей у каждого. Ну а если нужно написать код по сложнее, можно взять, например, питон. После опыта на C++ на нем можно начать писать уже через час после знакомства. Можно и perl, но у него все-же довольно инопланетный синтаксис покажется после C++. Поэтому мой выбор — питон. Но это уже на любителя.
Some people, when confronted with a problem, think
“I know, I'll use regular expressions.” Now they have two problems.
Я тоже жил и программировал без них лет 20. Когда встречался с ними — они мне казались то китайским языком, то абракадаброй, то магией. Конечно, я заглядывал в документацию, суть примерно была понятна, но… мозг просто отказывался их воспринимать. Просто не понимал, как такое можно использовать.
И все-таки не так давно я начал их понимать, использовать и очень этому рад. После некоторой привычки и практики они становятся понятными, и начинаешь ценить всю их мощь. То, что другими средствами выливается в десятки строк запутанного кода, можно записать регуляркой размером в полстрочки.
А что касается проблем… ну так на любом языке можно писать плохой код. Не надо писать стоэтажные регулярки, и тогда они будут простыми, понятными и надежными. Это — всего лишь инструмент. Причем очень эффективный инструмент, если применять его правильно и по назначению.
Сейчас, даже если я буду писать на том же C++, и увижу задачу, для которой хорошо подходят регулярные выражения — я буду использовать их и на C++.
Если вы читали приведённую ссылку, проблема была не в самом движке, а в
...poorly written regular expression that ended up creating excessive backtracking.
Разумеется, PCRE не даёт гарантий времени выполнения, но всё же чаще всего основной источник проблем — это сами выражения, который пишут «в лоб».
Некоторые другие движки могут давать гарантии и в чём-то могут быть быстрее, но не бесплатно — далеко не всё можно описать в растовском движке или re2 одним выражением или одним его выполнением.
У регулярных выражении длиннее 15 символов проблемы как с первым, так и со вторым.
Все эти утилиты внутри очень оптимизированны
По поводу оптимизации некоторых системных утилит, когда смотришь что они делают через strace — страшно становится. Взять хотя бы канонический пример, утилиту /bin/true, занимающую 25 килобайт! Да, я знаю, туда зашит слой совместимости для разных случаев, а так же хелп, но не нужно говорить, что он будет работать быстрее, чем самописный, оптимизированный для частного случая. Вообще, почти весь софт последних 50 лет был вдохновлён законом Мура и работает быстро только потому, что написан давно (под старые слабые процессоры) и делает довольно простые вещи, но то, что какая-то утилита выполняется за 0.5 миллисекунды не значит, что она быстрая, если тоже самое можно сделать за 0.05. Какое это имеет практическое значение? С точки зрения «мне нужно закрыть этот тикет к четвергу» — никакого. Но с точки зрения базовых кирпичиков, из которых строится фундамент, на котором будет стоять огромный небоскрёб кода — это имеет значение, потому что если фундамент гнилой — всё будет медленно и ресурсозатратно, что мы и видим почти во всех современных решениях. Одна утилита выполняет простое действие за 1мс вместо 0.01мс, она используется 50 раз в другой утилите, а та 50 раз в третей, которую вы вызываете из командной строки, в итоге вы имеете 2.5 секунды на отработку скрипта вместо возможных 25 миллисекунд. Собираясь вместе, например в сборке CI, такие утилиты набирают несколько минут, а то и часов, и мы идём пить кофе пока «оно компилируется». Из за того, что что-то занимает 25 килобайт, вместо 8кб при наивной реализации без лишнего функционала или 1кб при удалении clib и прочих неиспользуемых библиотек. Я не говорю про квадратичный рост вычислений, который тоже иногда проскальзывает, почитайте хотя бы что Джоэл пишет про zero terminated strings. Раньше тоже думал, что систему пишут очень умные ребята и у них всё оптимизировано. Они правда очень умные, но во многих местах улучшать не переулучшать, просто никто туда не лезет.
Хочу напомнить про yes
Все так, но время и качество разработки тоже учтите! Вот все эти утилиты полировались годами и десятилетиями и вероятность критичных багов в них… стремится к нулю. А вот Ваша персональная имплементация "yes" — с какой попытки Вы ее корректно напишете, учтете все corner-case? Т.е. — несомненно — нужно работу вести в обоих направлениях — и решать более сложные задачи на базе уже существующего фундамента в виде команд, платформ и фреймворков, а также заниматься написанием нового, но с умом, если это оправдано в конкретном случае. А не просто чтоб было. Примеров неудачных программ, созданных только из NIH — масса
Во-первых, привычный синтаксис.
Это пока вам не нужно часто делать что-то вроде
app | filter1 | filter2 | filter3
или даже банальном app1 || app2 && app3
замучаетесь с этим на «чистом» C++, да и многословнее гораздо — нечто простое вроде for x in $(find . -type f); do app $x; done
будет очень ветвисто.for pid in $(ps axuh|awk '{if ($6 > 3000 && $2 > 1) print $2}'); do
kill $pid
done
Помнить не обязательно, есть man.
Если бы я писал универсальный убиватель процессов для сообщества, я бы сделал всё иначе, с документацией, комментариями и даже (может быть) юнит-тестами, но баш ценен тем что можно быстро и просто сделать то что нужно в тот момент когда нужно, а не разворачивать целый проект с техзаданием, спецификациями и изучением рынка.
Если у меня возникла задача убить все процессы который жрут больше памяти чем нужно, я меньше чем за минуту это сделаю одной строчкой в баше, но на это уйдёт намного больше времени если в качестве языка для скриптов будет C++, не говоря уже о том что в системе где это может быть нужно может вообще не быть компилятора.
Как я уже отметил выше, если речь о чём-то переиспользуемом — то подход к написанию будет совсем другим, но в этом нет совершенно никакого смысла если проще написать эту самую программу в одну строчку с нуля, особенно если она нужна один раз в год.
Ну, если базовые "кирпичики" реализует кто-то другой — то все три примера можно будет перенести в C++ чуть ли не 1 в 1. А ваш 4й пример так даже проще получится.
Не говоря про то, что значительная часть скриптовых задач не решается на C++ без тонны кода на C или сторонних либ вроде boost, в этой статье сразу 3 пути по отстрелу ног в современном Linux.
Во-первых свои утилиты без пакетного менеджера класть в /bin довольно плохая идея, т.к. это может привести к конфликтам при обновлении релиза ОС и не только.
Во-вторых скрипт довольно очевидно небезопасный, поэтому выпускать его куда-либо дальше личной однопользовательской машины в таком виде не кажется хорошей идеей.
Ну и наконец на современных ОС в таком виде это в принципе будет нарушать работу ОС:
/usr/bin/c++ исторически принадлежит компилятору c++ (напрямую или через alternatives, зависит от системы). В последнее время /bin — симлинк на /usr/bin, соответственно в этом случае либо сломается скрипт (g++ не сможет его запустить), либо сломается сборка плюсов в сборочных системах, которые по умолчанию вызывают c++ из PATH
Знаю точно, что завтра многое забуду и через месяц опять буду гуглить
Я веду личную базу знаний для всего, что гуглится дольше 15 мин.
В общем, испытал небольшое разочарование в собственной неисключительности.Хмм… а я наоборот если вижу, что так уже кто-то сделал, думаю, что не так уж и туп раз пошёл по пути по которому уже кто-то сделал и работает :)
Может стоит добавить в хабы "ненормальное программирование?" Тогда всё встанет на свои места :)
Проблема еще оказывается в том, что зачастую не пишешь скрипт заново, а модифицируешь существующий, уже написанный кем-то. А он может быть не bash, а sh или еще что-то… Различия в синтаксисе есть, что работает в sh по идее должно работать и в bash, но не всегда наоборот. А если там dash или ash?
Отомстим им всем — запилим скрипт на плюсах, пусть удивляютцо ))) Шутю-шутю.
Вы жалуетесь на сложности с различными оболочками в скриптах, написанных различными другими людьми и как решение предлагаете нестандартный, но хорошо знакомый язык. А как оно решает проблему дебага уже готовых скриптов? Переписыванием их с нуля?
"— Всё это истинно. Тем не менее, в одной строчке баш-скрипта всё равно больше духа Unix, чем в десяти тысячах строк кода Си."
© Master Foo and the Ten Thousand Lines
В топике написано "можно ли..." — автор просто ответил на вопрос топика. А диспут разгорелся по вопросу "что лучше". Автор — молодец, плюсую. Больше интересных и спорных мыслей и постов!
Я должен подготовить «интерпретатор» скрипта c++. Написать его можно на чем угодно, да хоть на bash (это в последний раз, хотя не точно). Вот что у меня получилось.
Позволите непрошенное ревью?
#!/bin/bash
# Для такого простого скрипта можно и не требовать Bash,
# достаточно было бы /bin/sh.
msg_file=/dev/null
#msg_file=/dev/stdout
tmp_path=$HOME"/.cache/c++/"
# Тут бессмысленные кавычки. Чтобы от них была польза,
# стоило взять в них всю правую часть присваивания целиком.
mkdir -p $tmp_path
# Нужно так: mkdir -p "$tmp_path"
# Иначе пробелы в $HOME могут полностью изменить смысл
# команды.
tmp_file=$1".c++"
exe_file=$1".bin"
# Снова бессмысленные кавычки. Кроме того, неплохо было бы
# проверить, что переменная вообще установлена, прежде чем
# что-то компилировать.
if test $1 -nt $tmp_path$exe_file; then
# Вы же вроде в шебанге потребовали Bash, так зачем
# использовать test? И да, снова кавычки. Это в [[ строки
# можно не квотировать.
# Кроме того,
# у вас часто повторяется выражение "$tmp_path$exe_file",
# имеет смысл сделать для него переменную. А заодно добавить
# в конкатенацию слеш, чтобы логика не зависела от того,
# оканчивается ли $tmp_path на слеш или нет.
echo "Need to recompile.." > $msg_file
# Я бы просто убрал дебажные сообщения, чтобы не смущать
# читателя. Иллюстрации идеи они не помогают.
tail -n +2 $1 > $tmp_path$tmp_file
# Нужны кавычки. Кроме того, если вырезать из исходника
# первую строку, то диагностические сообщения компилятора
# станут указывать не туда. Мой вариант:
# echo -n "//" | cat -- - "$1" > "$tmp_path/$tmp_file"
eval "g++ -o $tmp_path$exe_file $tmp_path$tmp_file > /dev/null 2>&1"
# Я не совсем понял, для чего здесь eval?
# Почему бы просто не вызвать компилятор без него?
if [ $? -eq 0 ]
then
echo "Compiled ok" > $msg_file
else
echo "Compile error" > $msg_file
exit 255
fi
fi
eval "$tmp_path$exe_file $@1"
# Что значит конструкция "$@1"?
# Тут следует сделать как-то так:
# shift ; exec "$tmp_path/$exe_file" "$@"
#
# В этом случае запускаемая команда заменит собой запустивший её
# процесс и не возникнет проблем с перенапрвлением ввода и вывода
# внутрь и извне "скрипта".
Можно ли писать скрипты на C++?