Pull to refresh

Comments 73

а ещё есть csh, который, с одной стороны, полноценный шел, а с другой — в качестве языка в нем С
Можно ли писать скрипты на C++

Конечно можно, но зачем? Мне думается, что правильнее подбирать инструмент под задачу, а не задачу под инструмент. Для скриптинга у C++ слишком многословный и сложный синтаксис, а также относительно бедная стандартная библиотека.


Для этой задачи гораздо лучше подходит Python и компилировать ничего не надо. Добавить shebang #!/usr/bin/env python3 и запускать прямо из bash. И все возможности stdlib (pathlib, sys, os, shutil, re, argparse, ...) к вашим услугам. А если чего-то не хватает, pip install --user ... и погнали.

UFO just landed and posted this here
вроде std::vector, std::map или std::string т другие
std::filesystem (если компилятор позволяет)

конечно забавно скриптовать на C++, но в чем профит — непонятно. Скорости разработки оно не добавляет в сравнении с каким-нибудь питоном, пляски вокруг utf, стандартные контейнеры и filesystem крайне сомнительный профит. Быстрого подключения модулей нет ибо надо прокидывать все это дело компилятору. Предлагаю добавить хаб "ненормальное программирование"

Да, возможно это ненормальное программирование. Но насчет скорости, когда bash скрипт запускает из себя множество других процессов типа ls, sed, awk и прочие — а потом ждет их вывода для анализа — это что быстро работает?
да :) их (утилиты) шлифовали десятилетиями и они действительно быстро работают.

Ну и речь шла, опять же, про скорость разработки, а не исполнения скрипта.
Для таких придумали демоны типа speedy, которые второй раз не запускают утилиту.
Вот все хорошо в консольных утилитах, но до сих пор все они однопоточные. Это во времена когда за 10 лет в процессорах в однопотоке прибавились 30%.
Не видно даже подвижек чтобы хотя бы пакетные менеджеры использовали много потоков. Хотя бы для распаковки. И так во всем.
UFO just landed and posted this here
Рискованно производить установку во много потоков — потом можно не несколько дней потратить на восстановление системы. А многопоточная закачка давно есть в том же YUM/DNF.

А что касается многопоточной работы базовых утилит, не забываем, что работают они с ПОТОКАМИ. То есть каждой скармливается поток, с которым она что-то должна сделать. Если нужно делать что-то в несколько потоков, просто запускаете эти несколько потоков и не маетесь с последовательным запуском. Я так и делаю, причём, во всё том же Bash.

Установку не надо, а скачивание и распаковку — да. Все что может быть безопасно распараллелено.

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

Можно добавить опцию. Но пока всех устраивает скачивание за 5 минут вместо 1 и распаковку 10 вместо 2.

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

P.S. На моей машине и на моей сети закачка идёт в два потока. Хотя и не всегда. Видимо, зависит от загруженности сети и отдающего сервера.

У xargs есть отличный аргумент -P, который как раз и запускает несколько процессов параллельно. Не многопоточность, но загрузить проц до упора вполне можно. Например, для кодирования больших объёмов текста с помощью Sentencepiece или преобразования FB2 ⇒ TXT.

Можно, но xargs более вероятно будет установлен из коробки. Например, в дебиане он входит в findutils вместе с find (странная группировка, по-моему).

это что быстро работает?

Очень быстро, да. Но эксперимента ради предложил бы переписать утилиту yes из набора coreutils собственными силами и потом помериться в скорости. Там же голые Си под низом, которые отлаживали со второй половины прошлого века. В последней декаде решили еще бустануть, переписав на раст и сделав чуть более человечные API, кроссплатформенность и всякое разное прикольное (см. ripgrep, exa ). Но как упоминалось ниже — речь не про скорость работы, а про скорость разработки. Едва ли с быстренько можно написать какое-нибудь префиксное дерево чтобы вместо grep/sed использовать. Прототипировать может быть удобно, если из консоли не вылазить, но я обычно для такого использую sublime text с настроенной командой для REPL, а то и вовсе использую какой-нибудь wandbox.org или ideone.com. Так что это не та ситуация когда строгая типизация и манипулирование байтами имеет хоть сколько-нибудь значимое преимущество.
Второй нюанс — это все же не скриптование, а именно перекомпиляция, т.к. для полноценного скриптования нужен интерпретатор, вроде вышеупомянутого cling. Такое скриптование далеко не факт что работает сильно быстрее питона, если вообще быстрее.

UFO just landed and posted this here
Так-то оно, может и так, но прочтите статью внимательнее: автор в Linux ходит что-то вроде раз в месяц и ненадолго. То есть он каждый раз всё гуглит и изучает заново, о чём и написал, кстати. То есть для человека, который в *nix не живёт, но постоянно программирует на C++, это будет серьёзным ускорением работы. Собственно, я сам, используя Linux уже больше 20 лет, так и не осилил разобраться с awk, потому что быстрее построить этажерку из других утилит или перейти на другой язык. Возможно, зря не изучил, но пока нет повода закапываться ещё и в это. sed и тот использую исключительно как обработчик регулярных выражений, хотя знаю, что его возможности гораздо шире. Потому что, если припрёт, к моим услугам Python, на котором я, в основном, и работаю последние 7 лет.

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

Если называть питон чисто скриптовым языком — да, но я вроде только про скорость выполнения упоминал. Да и компиляция у питона ленивая. Можно было аналогично в противопоставление nodejs упомянуть. Тот еще медленнее да еще и по памяти прожорливее.
Что до sed/awk — для меня это обычно тоже темный лес. Т.к."сварщик я не настоящий" ибо пишу не с под vim в терминале и руки на мышку перекладываю иногда для моих нужд хватает find+grep с парой заученых ключей. Ну, и питон конечно же.

Не уверен, что сейчас ещё остались чисто скриптовые интерпретаторы, потому что проще «день потерять, зато потом за пять минут долететь». Особенно, если сохранять результат компиляции. Разница лишь в том, что питон начинает выполнение сразу, по ходу компиляции, а в варианте C++ надо сначала скомпилировать всю программу целиком.
скриптовые интерпретаторы

ванильный луа, насколько мне известно, не компилируется как питон с артефактами (про новое издание не знаю наверняка). У перла и js/nodejs вроде та же история. Многие лиспы. Преобразование в байткод своей внутренней виртуальной машины не считаю.


день потерять, зато потом за пять минут долететь

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

Разница в том, что питоноскрипт нормально кэширует сборку, а вот поделка автора статьи — кладет исполняемый файл в /tmp, что, конечно, при следующем запуске скрипта приведет к повторной компиляции. А как только автор втащит boost в свой "скрипт" на С++, то "привет, сосед!"


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

но в чем профит — непонятно.
Профит в повторном использовании кода книг Кернигана, Ритчи, Страуструпа & etc.
Уважаемый автор! С++ замечательный язык, один из моих любимых. Но… для каждой задачи надо использовать подходящий инструмент. Допустим, вам нужно обработать какой-то лог файл размером NN гигабайт, вытащить оттуда значения, сделать с ними какие-то несложные манипуляции, типа расчета средних, максимальных значений, вывода с сортировкой. Тот, кто владеет perl, grep, awk, sort и регулярками может написать и отладить такой скрипт минут за 15-20. Все эти утилиты внутри очень оптимизированны, начиная от буферного чтения, и заканчивая многопоточностью, если это необходимо. Мало того, они будут соединены в пайп, и работать одновременно, прокачивая через себя данные. То есть, многопоточность будет не только внутри них, но даже на уровне скрипта. Это скрипт легко может загрузить (эффективно!) все 8, или больше, сколько там у вас, ядер процессора.

Вот сколько вам понадобиться написать и отладить такой скрипт на 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++.

На самом деле проблем становится не две, а три+. Если не долбить ими что-то небольшое, то нормально, а если начинать заниматься каким-то массовым процессингом — молиться, чтобы движок регулярок был не слизан с перлового PCRE.

PCRE не слизан с перлового RE, это просто Perl Compatible RE, движок там совершенно другой, к перлу не имеющий никакого отношения (там только фичи и синтаксис общие) и, честно говоря, даже очень быстрый, особенно с учётом JIT.

Если вы читали приведённую ссылку, проблема была не в самом движке, а в
...poorly written regular expression that ended up creating excessive backtracking.

Разумеется, PCRE не даёт гарантий времени выполнения, но всё же чаще всего основной источник проблем — это сами выражения, который пишут «в лоб».

Некоторые другие движки могут давать гарантии и в чём-то могут быть быстрее, но не бесплатно — далеко не всё можно описать в растовском движке или re2 одним выражением или одним его выполнением.

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

Желательно чтобы код был не только быстронаписываемым, но еще легкопрочитываемым.
У регулярных выражении длиннее 15 символов проблемы как с первым, так и со вторым.
Эм… Вот тут Вы напомнили мне то, с чего я начинал работу в одной конторе: посадили переписывать генератор интерфейса RPC с Java на C#. То есть был генератор, который парсил документацию под интерфейсу и собирал код на Java, а надо было сделать такой же, но чтобы код был на C#. Он тогда только появился, так что wow-эффект у заказчиков только начинался и они страстно хотели именно C#. В общем, посмотрел я на чудовищные регулярки и отсторожно отошёл в сторонку. Потому что они там были за сотню символов длиной. И просто скопировал их в итоговый продукт, меняя только ту часть, которая склеивала код. Написано было, кстати, на Perl, из-за которого меня, новичка в конторе, и посадили за эту работу.
Не переживайте. Любой программист рано или поздно сталкивается с чужими регулярными выражениями и осознает что здесь нужно много времени чтобы понять и поменять
Насчёт оптимизации и отлаженности готовых инструментов полностью согласен, но вот именно отлаживать сами bash скрипты порой очень непросто, и всегда весьма неприятно.
Все эти утилиты внутри очень оптимизированны

По поводу оптимизации некоторых системных утилит, когда смотришь что они делают через 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" — с какой попытки Вы ее корректно напишете, учтете все corner-case? Т.е. — несомненно — нужно работу вести в обоих направлениях — и решать более сложные задачи на базе уже существующего фундамента в виде команд, платформ и фреймворков, а также заниматься написанием нового, но с умом, если это оправдано в конкретном случае. А не просто чтоб было. Примеров неудачных программ, созданных только из NIH — масса

Отличная идея. Ещё если ccache добавить, вообще супер будет.
Во-первых, привычный синтаксис.

Это пока вам не нужно часто делать что-то вроде
app | filter1 | filter2 | filter3
или даже банальном
app1 || app2 && app3
замучаетесь с этим на «чистом» C++, да и многословнее гораздо — нечто простое вроде
for x in $(find . -type f); do app $x; done
будет очень ветвисто.
А зачем конкретно в этом примере for x in $(find. -type f), если у find (по крайней мере, в GNU coreutils) есть параметр -exec?
Чисто для иллюстрации итерации по результатам вывода процесса. Но если это кажется слишком простым — ок, усложним:
for pid in $(ps axuh|awk '{if ($6 > 3000 && $2 > 1) print $2}'); do
  kill $pid
done

UFO just landed and posted this here

Помнить не обязательно, есть man.

UFO just landed and posted this here
Как уже выше ответили — есть man, но когда мне нужно «вот прям щаз» и что-то не вспоминается — я 15 секунд изучаю вывод «ps axu», смотрю колонки, и ещё 45 секунд пишу этот скрипт.

Если бы я писал универсальный убиватель процессов для сообщества, я бы сделал всё иначе, с документацией, комментариями и даже (может быть) юнит-тестами, но баш ценен тем что можно быстро и просто сделать то что нужно в тот момент когда нужно, а не разворачивать целый проект с техзаданием, спецификациями и изучением рынка.
UFO just landed and posted this here
Мы кажется где-то мы повернули не туда. Речь шла не о какой-то программе, которую пишут для кого-то, а о написании скриптов здесь и сейчас, под конкретную задачу, а также о способах и скорости их написания.

Если у меня возникла задача убить все процессы который жрут больше памяти чем нужно, я меньше чем за минуту это сделаю одной строчкой в баше, но на это уйдёт намного больше времени если в качестве языка для скриптов будет C++, не говоря уже о том что в системе где это может быть нужно может вообще не быть компилятора.

Как я уже отметил выше, если речь о чём-то переиспользуемом — то подход к написанию будет совсем другим, но в этом нет совершенно никакого смысла если проще написать эту самую программу в одну строчку с нуля, особенно если она нужна один раз в год.
Не всегда удобно использовать --exec. В скриптах, если есть возможность написать двумя методами следует писать так, как удобно.
В этом случае я xargs использую. +1 к понятности.

Ну, если базовые "кирпичики" реализует кто-то другой — то все три примера можно будет перенести в C++ чуть ли не 1 в 1. А ваш 4й пример так даже проще получится.

Не говоря про то, что значительная часть скриптовых задач не решается на C++ без тонны кода на C или сторонних либ вроде boost, в этой статье сразу 3 пути по отстрелу ног в современном Linux.
Во-первых свои утилиты без пакетного менеджера класть в /bin довольно плохая идея, т.к. это может привести к конфликтам при обновлении релиза ОС и не только.
Во-вторых скрипт довольно очевидно небезопасный, поэтому выпускать его куда-либо дальше личной однопользовательской машины в таком виде не кажется хорошей идеей.
Ну и наконец на современных ОС в таком виде это в принципе будет нарушать работу ОС:
/usr/bin/c++ исторически принадлежит компилятору c++ (напрямую или через alternatives, зависит от системы). В последнее время /bin — симлинк на /usr/bin, соответственно в этом случае либо сломается скрипт (g++ не сможет его запустить), либо сломается сборка плюсов в сборочных системах, которые по умолчанию вызывают c++ из PATH

Насколько я знаю, такая встроенная возможность (с shebang в первой строчке) есть в языке D.
Знаю точно, что завтра многое забуду и через месяц опять буду гуглить

Я веду личную базу знаний для всего, что гуглится дольше 15 мин.
UFO just landed and posted this here

С помощью приватного репозитория в github.

UFO just landed and posted this here
UFO just landed and posted this here
Живо представил как подключаешь к такому скрипту boost и………
image
В общем, испытал небольшое разочарование в собственной неисключительности.
Хмм… а я наоборот если вижу, что так уже кто-то сделал, думаю, что не так уж и туп раз пошёл по пути по которому уже кто-то сделал и работает :)
Проблема еще оказывается в том, что зачастую не пишешь скрипт заново, а модифицируешь существующий, уже написанный кем-то. А он может быть не 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" "$@"
# 
# В этом случае запускаемая команда заменит собой запустивший её
# процесс и не возникнет проблем с перенапрвлением ввода и вывода
# внутрь и извне "скрипта".
Sign up to leave a comment.

Articles