- комментарий 0:
From: kmeaw
Date: 2020-11-18 15:29:59Z
Я чаще всего использую awk для суммирования и группировок.
awk '{sum+=$1} END {print sum}'
awk '{sum+=$1; n+=1} END {print sum/n}'
awk '{acct[$1]+=$2} END {for(k in acct){print acct[k]" "k}}' | sort -nr
Чуть реже для разбора несложных текстовых формаов:
awk '
/^Name: / { name = $2 }
/^Version: / { version = $2 }
/^$/ { pkgs[name] = version; name=""; version="" }
END {
for(k in pkgs) { print k" "pkgs[k] }
}'
awk '($4 == "0A" && $2 ~ /:1389/){q=1} END {exit !q}' /proc/net/tcp{,6}
Стоит ли учить Perl? Стоит ли пытаться писать то же самое на Ruby?
- комментарий 1:
From: Sergey Matveev
Date: 2020-11-18 15:43:01Z
*** kmeaw [2020-11-18 18:24]:
>Стоит ли учить Perl? Стоит ли пытаться писать то же самое на Ruby?
По моему однозначно стоит. Всё что вы привели выше -- *так же легко*
делается в Perl. Плюсы Perl: он везде одинаков (ну, конечно, если не
разница в версиях 20-30 лет), в отличии от awk/gawk диалектов. Я помню
что в OpenWRT сборках perl весил 800 KB (не сильно тяжелее awk), при
этом в нём автоматом есть и средства для IPC, fork/exec-и, возможность с
работы с сетевыми сокетами (d4a676bfd4af613cdbd2391dc5a7f6a24f76a428).
Плюс редко можно встретить дистрибутив без Perl-а, в отличии от нужного
диалекта awk. Чего не сказать про Python/Ruby/Lua/Tcl и прочих.
Про Python точно скажу, про Ruby точно не уверен, но мне кажется там
однострочники не так легко и компактно можно писать (но про Ruby
запросто ошибаюсь, ибо мало что по нему помню, а сахара там хоть
отбавляй). Плюс Perl работает значительно быстрее Python/Ruby -- для
простых скриптов, которые внутри shell-а особенно исполняются: это критично.
Ну и вы наверное первый, кого я, так сказать, в живую встретил кто пишет
что-то сложнее $XX в awk. Тогда как людей знающих (само собой только на
каком-то базовом уровне, как и я, ибо я даааавно не писал на нём что-то
большое) Perl во много раз больше. Ваши примеры на Perl отлично почти
точно также пишутся. Даже визуально я с ходу бы не сказал что у вас не
Perl, в котором те же BEGIN/END, наверняка взятые из awk, имеются.
- комментарий 2:
From: Sergey Matveev
Date: 2020-11-18 15:47:31Z
*** Sergey Matveev [2020-11-18 18:42]:
>По моему однозначно стоит. Всё что вы привели выше -- *так же легко*
>делается в Perl.
Я точнее хотел сказать что если это вы только лично для себя используете
(ну как у каждого свои фишки в shell-ах), то тут вам Perl может ничего и
не даст. А вот если в скриптах использовать, то я считаю awk давно пора
похоронить.
- комментарий 3:
From: kmeaw
Date: 2020-11-18 16:51:54Z
> Я помню что в OpenWRT сборках perl весил 800 KB (не сильно тяжелее awk)
В OpenWRT используется busybox. Мне нравится использовать busybox везде,
где это возможно из-за его компактности, а awk в нём уже есть.
> А вот если в скриптах использовать, то я считаю awk давно пора
> похоронить.
Тут сложно не согласится. Если в shell-скрипте нужен awk, сложнее чем
$XX, то его сразу стоит писать на нормальном языке.
> Плюсы Perl: он везде одинаков
Это, конечно, круто. Список поддерживаемых платформ впечатляет.
Пытался вспомнить хоть что-нибудь плохое про Perl, и в голову пришла
история одного бага.
Я обслуживал систему, в которой на сотнях машин из cron запускались
десятки perl-скриптов. В качестве ОС использовалась Ubuntu, где perl
(как и в большинстве современных ОС) собран с USE_ITHREADS. Эта опция
приводит к тому, что во многие служебные структуры интерпретатора Perl
добавляется mutex, который защищает их во время конкурентного доступа.
В качестве системной библиотеки конечно же использовалась glibc.
Оказывается, в реализации clone(2) в Linux есть особенность, из-за
котроой флаг CLONE_CHILD_SETTID (записать tid создаваемого потока в его
TCB, который находится в TLS) не всегда отрабатывает правильно.
Разработчики glibc про эту особенность знают, поэтому запоминают tid
родителя перед clone, а затем в новом потоке делают что-то вроде
assert(my_pid != parent_pid).
Эта особенность проявляется под большими нагрузками, когда в системе за
единицу времени создаётся и умирает большое число потоков, выделяющих
и освобождающих память с помощью mmap. Не помню уже точно, но кажется
условием воспроизведения также было использование copy-on-write.
В Perl есть конструкция `cmd`, которая, как и в shell, возвращает stdout
порождённого процесса. Чтобы защищаться от гонок, Perl обкладывает все
вызовы fork блокировкой важных mutex'ов и последующей разблокировкой
(как в дочернем, так и в родительском процессах). `cmd` вызывает
Perl_pp_backtick, который вызывает Perl_my_popen, который вызывает
PerlProc_fork, который вызывает fork, который является обёрткой над
clone, включающей в том числе флаг CLONE_CHILD_SETTID.
Внутри glibc срабатывает assertion failure, осталось напечатать
сообщение об ошибке и завершить работу процесса. Но для печати
используется функция asprintf, которая вызывает malloc, которая уходит в
deadlock.
Эту ошибку можно исправить тремя способами: собрать perl без
USE_ITHREADS, убрать динамическое выделение памяти внутри assert или
поправить clone, чтобы он возвращал ошибку при невозможности обновить
tid.
Я пытался искать какие-либо упоминания об этой проблеме, и нашёл лишь
похожие deadlock в списках рассылки Ocaml. Эта проблема воспроизводится
на Perl 5.14.2 и не воспроизводится на 5.22.1, так что я склонен считать
её проблемой Perl, а не glibc или ядра.
- комментарий 4:
From: Sergey Matveev
Date: 2020-11-18 17:34:32Z
*** kmeaw [2020-11-18 19:46]:
>В OpenWRT используется busybox. Мне нравится использовать busybox везде,
>где это возможно из-за его компактности, а awk в нём уже есть.
Один из диалектов :-). Это как sed -- даже -i опция работает по разному
в BSD и GNU системах.
>Это, конечно, круто. Список поддерживаемых платформ впечатляет.
Главное что Perl это Perl. Нет GNU Perl, BSD Perl, Solaris Perl или
каких-то других.
>Пытался вспомнить хоть что-нибудь плохое про Perl, и в голову пришла
>история одного бага.
История классная. Но... не то чтобы я предвзят, но разве fork не должен
просто штатно возвращать ошибку (-1) если что-то не так? Я быстро глянул
в исходный код Perl и вижу что он честно делает fork(). Как там он
внутри устроен (clone или whatever) его, очевидно, не должно волновать.
Ошибку не возвращают, вместо этого делая assert. Но такой assert, что он
не может себя распечатывать из-за влияния mutex-ов/malloc-ов. Насколько
понимаю, речь про эту багу:
https://www.nntp.perl.org/group/perl.perl5.porters/2014/03/msg213887.html
где считают это косяком glibc.
https://www.nntp.perl.org/group/perl.perl5.porters/2014/03/msg214090.html
"Assuming pthread_atfork is broken, since this is glibc" :-)
https://sourceware.org/legacy-ml/libc-alpha/2013-01/msg01051.html
Но в Perl решили пойти навстречу корявым реализациям, насколько понял, и
сделать workaround. Мне нравится другой подход -- исправлять и ждать
когда исправят нерабочие вещи. В Go вот была проблема с /dev/log syslog
логгером под Linux: https://github.com/golang/go/issues/5932
который мог повиснуть. Пару лет обсуждали и переносили возможное
исправление (workaround), но тут пришёл Фитчпатрик и закрыл "due to
timeout" :-). Когда я только начинал на Go программировать, то под Linux
это действительно была проблема и я видел что это чуть ли не причина #1
почему люди не используют родной syslog logger. Сейчас не знаю как с
этим обстоят дела, не следил, но вроде как это kernel bug, который был
исправлен.
- комментарий 5:
From: kmeaw
Date: 2020-11-18 20:22:43Z
> разве fork не должен просто штатно возвращать ошибку (-1) если что-то
> не так
Сложный вопрос. Коллега пытался это исправить в ядре. Торвальдс сказал,
что не стоит добавлять в это конкретное место проверку вызова put_user,
потому что есть куча других мест, где этой проверки нет, и юзерспейс
привык к такому поведению. Он готов принять патч с проверкой, если она
будет добавлена во все подобные места, чего никто пока не сделал.
И в чём-то он, наверное, прав. Ведь основнаяя зачада fork() выполнена -
создан новый процесс. А то, что у него в TLS что-то не записано, уже не
является проблемой ядра. И вообще, layout'ом TCB должна заниматься
библиотека, реализующая threading, а не ядро.
В glibc есть довольно много мест, где в случае нарушения инварианта не
возвращают ошибку, а сразу падают с assert. Недавно нашей команде
пришлось переписывать код работы с netlink, который тянется от
getifaddrs(3), вызываемый используемым нами pubsub-фреймворком (чтобы
сообщить координатору все IP-адреса, по которым доступен subscriber).
Если из-за ошибки в программе кто-то делал close на чужой (другого
потока) файловый дескриптор, и glibc не могла из-за этого получить ответ
от netlink-сокета, то программа немедленно разваливалась.
С одной стороны это хорошо, так как в этот момент программа находится в
неопределённом состоянии из-за ошибки программиста, а значит может
произойти всё, что угодно, и безопаснее всего будет развалить программу
окончательно.
С другой стороны это не даёт возможности программисту ограничить домен
ошибки и перезапустить повреждённую часть приложения.
Похожая ситуация есть в libX11 - если что-то пошло не так, то программа,
использующая библиотеку, просто завершается с ошибкой. К счастью,
разработчики предусмотрели возможность переопределить этот обработчик
по-умолчанию своим (но мне всё равно это не нравится, асинхронная
обработка исключительных ситуаций почти всегда приводит к беде).
Примерно те же вопросы поднимаются в обсуждениях return err vs
panic(err) в Go-коде.
- комментарий 6:
From: Sergey Matveev
Date: 2020-11-18 20:32:35Z
*** kmeaw [2020-11-18 23:17]:
>Он готов принять патч с проверкой, если она
>будет добавлена во все подобные места, чего никто пока не сделал.
Либо всё, либо ничего -- тоже одобряю и понимаю.
>В glibc есть довольно много мест, где в случае нарушения инварианта не
>возвращают ошибку, а сразу падают с assert.
Кстати, suckless у себя на сайте поливают грязью GMP, потому что если
памяти не хватает, то он делает abort(). Я в своей небольшой C программе
делаю assert после каждого malloc, предполагая что если уж даже для неё,
такой небольшой, не хватает памяти, то и нефиг работать.
>Примерно те же вопросы поднимаются в обсуждениях return err vs
>panic(err) в Go-коде.
В целом согласен что тут однозначного ответа нет. И за и против, как вы
и привели. В Go коде который я делаю на коленке -- чуть что, сразу
паникую, когда смысла продолжать программу нет. Но а когда нормальное
что-то пишу, то панику вызываю... с ходу вот только вспоминаю когда
делаю blake2b.New(nil), который обязан уж точно отработать, а если нет,
то значит это что-то совершенно не работающее. Хотя не, есть места где я
panic оставляю, по сути, как assert в C -- то, что по идее никогда не
должно случится, либо при правильном использовании не должно случится:
например не возвращать error что длина ключа кривая, а сразу паниковать
-- один раз пользователь библиотеки поправит у себя вызов, и дело с
концами, проблем нет. Но вот тут то как-раз и порождается целое поле
когда и что и как стоит использовать -- снова согласен :-)