Этот сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Ещё немного про random() в bc

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

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

Сегодня, пока ехал в такси, придумывал ещё более так себе способы это сделать.

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

#!/usr/bin/env -S -i seed_="0${XDG_SESSION_ID};while(seed_--).=random()#" bc /proc/self/environ
print random()
quit()

Здорово, правда?

Вся чёрная магия сосредоточена в первой строке. Файл /proc/self/environ формируется в «Линуксе» для каждого запущенного процесса и содержит унаследованные переменные окружения. Так как там много ненужного нам мусора, очищаем его целиком при помощи ключа -i утилиты env.

Далее мы создаём переменную окружения seed_, а внутри её формируем небольшую программу, которая запустится до нашего кода. Для bc результат будет выглядеть так:

seed_=0123456
while(seed_--).=random()
#^@
print random()
quit()

Я развернул односрочную программу в многострочную для удобства.

Вместо 123456, конечно, будет подставлено число из переменной окружения $XDG_SESSION_ID, которая содержит число. Ноль там впереди, чтобы не случилось ничего страшного, если эта переменная в вашем дистрибутиве «Линукс» отсутствует.

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

В конце файла /proc/self/environ всегда есть символ с кодом ноль, который мог бы нам всё испортить, но мы спрятали его за символом комментария — #.

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

Несложно додуматься до похожего способа, который позволяет читать число из какого-нибудь файла в системе. Нужно только, чтобы оно было там одно. Надо создать файл с содержимым seed_=\ и подключить его до файла с числом. Файлы подключаются через перевод строки, действие которого отменяет слеш, так что число, опять же, попадёт в переменную seed_. Дальше всё как в предыдущем примере.

9 комментариев
al.zatv 1 мес

есть же файл /dev/random для генерации почти честных случайных чисел:
https://ru.wikipedia.org/?title=/dev/random_%D0%B8_/dev/urandom

Евгений Степанищев 1 мес

Есть, конечно, но чем он мне поможет-то? Числа-то там в бинарном виде и сплошным потоком. В bc это ни прочитать, ни отрезать.

al.zatv 1 мес

вот рандом загонит в bc (thx to deepseek):

dd if=/dev/random bs=4 count=1 status=none | od -An -t u4 | bc

Евгений Степанищев 1 мес

Мне свободный stdin нужен. Как дальше с программой-то взаимодействовать? То, что подсказал ДипСик, можно сделать куда проще: bc <<<$RANDOM.

al.zatv 1 мес

наверное можно и в env засунуть.
кстати, оригинальная идея,в shebang вставить запуск чего-то многоэтажного. ещё не видел такого.

Евгений Степанищев 1 мес

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

hsh 1 мес

bc под «Линуксом» очень старый

Не самый плохой вариант!

$ bc ––version
fish: Unknown command: bc

Евгений Степанищев 1 мес

Надо же :-) И dc тоже нет?

hsh 1 мес

https://unix.stackexchange.com/a/636196

похоже в баше таки есть таймстамп

$ echo ${EPOCHSECONDS}
1738875578
$ echo ${EPOCHREALTIME}
1738875599.715386
$ env -S -i mtest="${EPOCHSECONDS}"
mtest=1738875693

Евгений Степанищев 1 мес

Такое только из командной строки работает, там где шелл есть. При запуске через #! строку обрабатывает сам env и такие фокусы не проходят.

А так-то и более пригодная переменная есть — $RANDOM.

hsh 1 мес

Надо же :-) И dc тоже нет?

неа :)
$ dc
fish: Unknown command: dc

Но вообще в арче похоже свежий bc: bc 1.08.1-1, 2025-01-05

Ещё в моём wayland этот «рандом» бы тоже работал так себе: XDG_SESSION_ID=1 :)

Евгений Степанищев 1 мес

Но вообще в арче похоже свежий bc: bc 1.08.1-1, 2025-01-05

Это тоже очень старый, на самом деле, на «Маке» — 6.5.0. Такое ощущение, что bc на две ветки развалился — линуксовый, который не развивается, и… ну… фришный, который на «Маке»; там видно развитие.

Ещё в моём wayland этот «рандом» бы тоже работал так себе: XDG_SESSION_ID=1 :)

Хах :-) А cat /proc/self/sessionid что выдаёт?

hsh 1 мес

с форматирование в эгее, конечно, своя атмосфера

Евгений Степанищев 1 мес

Какой-то сильно ограниченный маркдаун.

hsh 1 мес

Такое ощущение, что bc на две ветки развалился — линуксовый, который не развивается, и… ну… фришный, который на «Маке»;

Похоже что в линуксе gnu bс (https://ftp.gnu.org/gnu/bc/), а у вас там bsd bc (https://git.gavinhoward.com/gavin/bc)

Хах :-) А cat /proc/self/sessionid что выдаёт?

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

$ cat /proc/self/sessionid
1

Евгений Степанищев 1 мес

Понятно :-) Надо ещё какую-нибудь переменную искать, в выдаче env ничего нет, содержащего только цифры?

hsh 1 мес

Надо ещё какую-нибудь переменную искать, в выдаче env ничего нет, содержащего только цифры?

У меня есть вот эти две, но я бы не надеялся на них (возможно что без моего терминала их не будет):
WINDOWID=108603892187776
ALACRITTY_WINDOW_ID=108603892187776

Но когда я залогинился в текстовую консоль, то стало XDG_SESSION_ID=3! Надо просто перед запуском залогиниться пару тысяч раз!

Евгений Степанищев 1 мес

У меня есть вот эти две, но я бы не надеялся на них…

Да у меня скорее размышления и эксперименты, а не готовые решения, я всё же не учебник по bc пишу :-)