Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

shell $RANDOM seed not honored in pipelines

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?

like image 947
lqu Avatar asked Jan 11 '23 11:01

lqu


2 Answers

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).

like image 107
Charles Duffy Avatar answered Jan 19 '23 06:01

Charles Duffy


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.

like image 42
lqu Avatar answered Jan 19 '23 08:01

lqu