26

I'm looking for ways to use /dev/random (or /dev/urandom) from the command line. In particular, I'd like to know how to use such a stream as stdin to write streams of random numbers to stdout (one number per line).

I'm interested in random numbers for all the numeric types that the machine's architecture supports natively. E.g. for a 64-bit architecture, these would include 64-bit signed and unsigned integers, and 64-bit floating point numbers. As far as ranges go, the maximal ranges for the various numeric types will do.

I know how to do all this with all-purpose interpreters like Perl, Python, etc., but I'd like to know how to do this with "simpler" tools from the shell. (By "simpler" I mean "more likely to be available even in a very minimal Unix installation".)

Basically the problem reduces that of converting binary data to their string representations on the command line. (E.g., this won't do: printf '%f\n' $(head -c8 /dev/random).)

I'm looking for shell-agnostic answers. Also, the difference between /dev/random and /dev/urandom is not important for this question. I expect that any procedure that works for one will work for the other, even when the semantics of the results may differ.


I adapted EightBitTony's answer to produce the functions toints, etc. shown below.

Example use:

% < /dev/urandom toprobs -n 5
0.237616281778928
0.85578479125532
0.0330049682019756
0.798812391655243
0.138499033902422

Remarks:

  1. I'm using hexdump instead of od because it gave me an easier way to format the output the way I wanted it;
  2. Annoyingly though, hexdump does not support 64-bit integers (wtf???);
  3. The functions' interface needs work (e.g. they should accept -n5 as well as -n 5), but given my pitiful shell programming skillz, this was the best I could put together quickly. (Comments/improvements welcome, as always.)

The big surprise I got from this exercise was to discover how hard it is to program on the shell the most elementary numerical stuff (e.g. read a hexadecimal float, or get the maximum native float value)...


_tonums () {
  local FUNCTION_NAME=$1 BYTES=$2 CODE=$3
  shift 3

  local USAGE="Usage: $FUNCTION_NAME [-n <INTEGER>] [FILE...]"
  local -a PREFIX

  case $1 in
    ( -n ) if (( $# > 1 ))
           then
               PREFIX=( head -c $(( $2 * $BYTES )) )
               shift 2
           else
               echo $USAGE >&2
               return 1
           fi ;;
    ( -* ) echo $USAGE >&2
           return 1 ;;
    (  * ) PREFIX=( cat ) ;;
  esac

  local FORMAT=$( printf '"%%%s\\n"' $CODE )
  $PREFIX "$@" | hexdump -ve $FORMAT
}

toints () {
  _tonums toints 4 d "$@"
}

touints () {
  _tonums touints 4 u "$@"
}

tofloats () {
  _tonums tofloats 8 g "$@"
}

toprobs () {
  _tonums toprobs 4 u "$@" | perl -lpe '$_/=4294967295'
}
5
  • 2
    tr -cs '[:digit:]' '[\n*]' </dev/urandom should give you integer number only.
    – cuonglm
    Commented Mar 10, 2016 at 15:59
  • as far as I am aware, /dev/random do not generate random numbers per se, but add noise to the mathematical functions that give pseudo-random number for them to become less predictable. You might want to look at randomlib.sourceforge.net Commented Mar 10, 2016 at 16:01
  • 2
  • Do you need crypto-quality randomness, or is e.g. seeding based on the time good enough? Commented Mar 10, 2016 at 22:45
  • @Gilles: the motivation for my question was more to find out about shell and Unix tools than about randomness; I just wanted to know what basic Unix tools existed for converting a stream of binary data into their string representation.
    – kjo
    Commented Mar 11, 2016 at 1:36

6 Answers 6

35

You can use od to get numbers out of /dev/random and /dev/urandom.

For example,

2 byte unsigned decimal integers,

$ od -vAn -N2 -tu2 < /dev/urandom
24352

1 byte signed decimal integer,

$ od -vAn -N1 -td1 < /dev/urandom
-78

4 byte unsigned decimal integers,

$ od -vAn -N4 -tu4 < /dev/urandom
3394619386

man od for more information on od.

0
14

Some shells (e.g. bash(1)) have a $RANDOM "variable" that gives random numbers.

3
  • 1
    Keep in mind, that's 15-bit.
    – Pourko
    Commented Nov 4, 2020 at 3:21
  • 1
    $SRANDOM offer a 32 bit range
    – Alfred.37
    Commented Aug 17, 2021 at 12:18
  • SRANDOM is new as of bash 5.1, many users are still using older versions. Commented Aug 27 at 19:31
10

It it much more efficient to work with the Bash built-ins than shell out to external commands like od. Here is for example how you can get a 60-bit random number:

((RND=((RANDOM<<15|RANDOM)<<15|RANDOM)<<15|RANDOM))

That will give a random number in the range of 0 to 1,152,921,504,606,846,975. You can scale that down with a modulo division to whatever range you want.

As a practical example, let's say, I want to read one random sector from my hard disk, to wake up the disk from a standby state. I can do this:

#!/bin/bash
DEV=/dev/sda  # Let's say that this is the device we want to spin up.

# Get the device geometry...
read -d- SIZE64 BS <<<$(blockdev --getsize64 --getbsz $DEV)
((SECTORS=SIZE64/BS)) # The total number of $BS-sized sectors on that device.

((RND=(RANDOM<<15|RANDOM)<<15|RANDOM)) # Generate a 45-bit random number.
((SECT=RND%SECTORS)) # The random sector that we will now read...

dd if=$DEV of=/dev/null bs=$BS skip=$SECT count=1 >/dev/null 2>&1

Done.

(Note: In this example I decided that a 45-bit random integer would be more than sufficient, and also a tiny little bit faster than the 60-bit one.)


Update:

To give some quantitative backup for the above speed claim:


~# time for i in {1..10000} ;do RND=$(od -An -N7 -tu8 /dev/urandom) ;done

real    0m45.647s
user    0m17.540s
sys     0m28.807s

~# time for i in {1..10000} ;do ((RND=((RANDOM<<15|RANDOM)<<15|RANDOM)<<15|RANDOM)) ;done

real    0m0.112s
user    0m0.105s
sys     0m0.007s

0
4

You could do something like:

perl -le '
  while (q(
    c char,  C unsigned char, s! short, S! unsigned short,
    i! int,  I! unsigned int, l! long,  L! unsigned long,
    f float, d double,) =~ /(\S+) (.*?),/gs) {
    $size = length(pack $1, 0);
    sysread STDIN, $data, $size;
    print "$2($size): " . unpack($1, $data);
  }' < /dev/urandom

Which on a 64 bit system would give you something like:

char(1): -98
unsigned char(1): 62
short(2): -12526
unsigned short(2): 399
int(4): 499066219
unsigned int(4): 2312134917
long(8): -4889591208978026255
unsigned long(8): 2080566823379835456
float(4): 55.4727554321289
double(8): 8.6395690272822e-05
3
# Output 1 random value between 1 and 100
shuf --random-source='/dev/urandom' -n 1 -i 1-100;
# 57

By default these commands use an internal pseudo-random generator initialized by a small amount of entropy, but can be directed to use an external source with the --random-source=file option. An error is reported if file does not contain enough bytes. For example, the device file /dev/urandom...

Source: https://www.gnu.org/software/coreutils/manual/html_node/Random-sources.html


Some examples:

shuf --random-source='/dev/urandom' -n 100 -i 1-5 | perl -pe 's/\n/ /g;';
# 2 1 5 4 3

shuf --random-source='/dev/urandom' -n 1 -i 1-18446744073709551615;
# 3784824711059790616

shuf --random-source='/dev/urandom' -n 1 -i 1-18446744073709551616;
# shuf: invalid input range: ‘18446744073709551616’: Value too large for defined data type

shuf --random-source='/dev/urandom' -n 1 -i 0-18446744073709551615;
# shuf: invalid input range: ‘0-18446744073709551615’

shuf --random-source='/dev/urandom' -i 1-18446744073709551615;
# shuf: memory exhausted

It's worth to state the following:

shuf -i 2-0; echo $?;
# shuf: invalid input range: ‘2-0’
# 1

shuf -i 1-0; echo $?;
# 0

Related: https://news.ycombinator.com/item?id=14846285 (Careful, shuf is not cryptographically safe by default...)

1

bash has since version 5.1 or 5.2 a $SRANDOM "variable" that gives 32 bit random numbers.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .