2021-12-12 / Bartłomiej Kurek
Losowe dane w skryptach shell

$RANDOM

Nie wszystkie shelle wspierają generowanie losowych danych. W niektórych wystarczy użyć zmiennej $RANDOM, która za każdym razem zawiera losową liczbę. Przykładowo bash:

$ bash -c 'echo $RANDOM'
22139

Bash w trybie POSIX:

$ bash --posix -c 'echo $RANDOM'
14343

ksh:

$ ksh -c 'echo $RANDOM'
11421

Busybox (w Alpine Linux):

$ docker run -it --rm alpine
/ # ls -la $(command -v $0)
lrwxrwxrwx    1 root     root            12 Nov 24 09:20 /bin/sh -> /bin/busybox
/ # echo $RANDOM
13196

ZSH:

$ zsh -c 'echo $RANDOM'
18535

Ale przykładowo w dash (zlinkowany do /bin/sh w Debian/Ubuntu) już to nie zadziała.

$ dash -c 'echo $RANDOM'

$
$ sh -c 'echo $RANDOM'

$

ash również tego nie implementuje:

$ ash -c 'echo $RANDOM'

$

Oczywiście - można zapytać: "kto używa ash?". Ale daleko szukać nie trzeba - logujemy się np. na router i sprawdzamy:

rtr $ echo $RANDOM

rtr $ 
rtr $ head -2 /etc/os-release 
NAME="OpenWrt"
VERSION="19.07.7"
rtr $ echo $SHELL
/bin/ash

Systemów i urządzeń jest wiele. Jest cała rodzina systemów BSD i innych systemów Unix, jest Android, są systemy embedded i choćby mnóstwo rozwiązań na raspberrypi.

Zmienna $RANDOM nie znajduje się w specyfikacji POSIX. Nie jest to oczywiste, wystarczy spojrzeć na powyższy przykład "bash --posix", gdzie to rozszerzenie - mimo wszystko - działa. Jak sobie z tym poradzić?

Sposobów pewnie jest wiele, niemniej - nie są one intuicyjne. Jeśli mamy np. coreutils, to możemy użyć zawiłej komendy:

$ expr $(cat /dev/random | tr -dc '[:digit:]' | fold -w 8 | head -n 1)
96089464

Jeśli chcemy uzyskać liczbę, to powyższy przykład wyciąga dane z /dev/random, usuwa wszystko co nie jest cyfrą, zawija to do 8 znaków i pobiera tylko pierwszą linię. Całość oczywiście traktujemy "expr", aby uzyskać właśnie wartość liczbową.

expr

$ x=$(cat /dev/random | tr -dc '[:digit:]' | fold -w 8 | head -n 1)
$ y=$(expr $x + 1)
$ echo $x
39875261
$ echo $y
39875262
$ expr $y / 17
2345603
$ expr 2345603 \* 17  # !!!
39875251
$ test $y -eq $(expr 2345603 \* 17) && echo "OK" || echo "NOT OK"
NOT OK
$ expr 2 \* 2
4

/dev/random, /dev/urandom

/dev/random znajdziemy w każdym systemie klasy Unix zgodnym z POSIX.
/dev/urandom znajdziemy między innymi w Linux.

Obu możemy użyć w celu uzyskania wartości losowych. Przykładowo pobieramy 4 bajty:

$ dd if=/dev/random bs=4 count=1 
*A��

Lub 10 ciągów tekstowych po 13 bajtów:

$ cat /dev/random | tr -dc '[:alnum:]' | fold -w 13 | head -n 10
MZIb47KmWbKdS
MCiiUbkayu6Ph
tTfvZrr3wfINz
7iFfjWoptpbdg
hvnP5SB70M6sq
2ZnsbW35YG4tO
MmIJG1wXnVRTX
v7reK60nRBlkY
JoTpVaBdLRedt
xYO1FDn6EGyWT

Możemy tak wygenerować np. losowe hasło (tutaj o długości 32 znaków /fold/):

$ passwd=$(cat /dev/urandom | tr -dc '[:alnum:][:punct:]' | fold -w 32 | head -n 1)
$ echo $passwd
,]w0[18PR[ABu{\[[2\UIr\*WPvVA#"g

uuidgen

Jeśli mamy do dyspozycji komendę uuidgen (w Debian pakiet uuid-runtime), to możemy generować dane typu UUID.

$ uuidgen 
3f688742-1a1c-49d4-8d0a-350c4081095c
$ uuidgen -r
48be214c-2d0f-49e8-8938-67c80196652c

openssl rand

Jeśli mamy zainstalowany openssl, to zawiera on moduł "rand".

$ openssl rand --base64 32
bNarpISMYllpF57OPKlJqRv0/AF9CxgFhRWLPYvGi5Q=
$ openssl rand --base64 16
gPQ7NQisE8qgbqZeyS83WA==
$ openssl rand -hex 16
913a5095e511c284ade88f08a87b905f
$ openssl rand -hex 16
b8a57293511be096ed624393c615c685
$ openssl rand -hex 32
5cdca35831646ef3002b07b7a38b6dcca0b99863747e24e1feea471f19312074

Moglibyśmy również wykorzystać /dev/urandom w połączeniu z modułem openssl hashującym hasła (openssl passwd).

$ cat /dev/urandom | fold -w 12 | head -1 | openssl passwd -stdin
fUorK1vjHPX9A
$ cat /dev/urandom | fold -w 12 | head -1 | openssl passwd -stdin
PrNL4677tRVE2
$ cat /dev/urandom | fold -w 12 | head -1 | openssl passwd -apr1 -stdin
$apr1$TO5r5jkn$u4nXlcgy1BV6IGR8/VVxw0
$ cat /dev/urandom | fold -w 12 | head -1 | openssl passwd -apr1 -stdin
$apr1$imscoJsr$0/8l8QeM5iQf3YF3oprwL0

Sposobów na pewno jest więcej (można np. uruchomić jakiś interpreter), ale każdy z nich ma swoje jasne i ciemne strony (np. odczyt z /dev/random może się zablokować). POSIX specyfikuje m.in. funkcje (srand(), rand(), rand_r(), random()), a jednak zdaje się, że nie ma (?) uniwersalnej komendy, która po prostu wydaje losową wartość wylosowaną np. funkcją random().