4

This is a strange behavior I can't explain. I want to use shell to generate a predictable random number sequence. I use $RANDOM with a seed. Here is a test program.

RANDOM=15
echo $RANDOM

This works fine by giving the same number every time I run it. But if I add a pipe to this program it gives different results every time. Try the following simplified program.

RANDOM=15
echo $RANDOM | cat

I have found 2 fixes to the problem (making it predictable), but still can't explain why.

Fix 1

RANDOM=15
x=$RANDOM
echo $x | cat

Fix 2

(RANDOM=15
echo $RANDOM) | cat

I tried on Linux and Mac. The behavior is consistent. Can somebody explain?

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
lqu
  • 606
  • 9
  • 14
  • 2
    FYI -- `RANDOM` is not part of the POSIX sh baseline shell language, but an extension available in bash (and probably other modern ksh derivatives). This should probably thus be tagged `bash`, not `shell`, and you should avoid using `RANDOM` in scripts starting with `#!/bin/sh`, as it's not guaranteed to work with all `/bin/sh` interpreters. – Charles Duffy Jun 05 '14 at 01:31

2 Answers2

7

Pipelines, as in echo $RANDOM | cat, create subshells -- separate processes forked from the parent but not replaced with a different executable image using an exec()-family call. You're observing a difference in behavior between the shell in which RANDOM is explicitly set, and subshells forked from same.

Your workarounds either move the evaluation of $RANDOM out of a subshell into the parent (first case), or move the explicit seed set into the subshell (second case).

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • 2
    +1; to be explicit about what may be non-intuitable: BOTH segments of pipeline `echo $RANDOM | cat` run in subshells. – mklement0 Jun 05 '14 at 01:34
2

Thank you Charles Duffy for pointing to the right direction (subshell). I found in the src code of bash, there is file variable.c . $RANDOM is a "dynamic variable", to get the value a function is called; and the function re-seeds the random generator when $RANDOM is first evaluated in the subshell.

// from bash-4.3/variables.c

int
get_random_number ()
{
  int rv, pid;

  /* Reset for command and process substitution. */
  pid = getpid ();
  if (subshell_environment && seeded_subshell != pid)
    {
      seedrand ();    // <<<<==== re-seed!
      seeded_subshell = pid;
    }

  do
    rv = brand ();
  while (rv == last_random_value);
  return rv;
}

Seed is a static variable, so each shell has its own copy. Re-seeding in the subshell has no effects in the parent. Here is another test case to show $RANDOM reference in subshell has nothing to do with the sequence in parent shell.

RANDOM=15
echo $RANDOM $RANDOM
RANDOM=15
echo $RANDOM | cat
echo $RANDOM

The last line gives the first random number after 15.

Community
  • 1
  • 1
lqu
  • 606
  • 9
  • 14
  • That changes in a subshell don't impact the parent is no surprise -- it would take a significant amount of effort/care to implement contrary behavior (such as using a shared-memory segment). Finding the code for the reseed is a nice touch, though; +1! – Charles Duffy Jun 05 '14 at 19:31