Difference in random values generation in zsh and bash subshells - bash

I am not able to understand why zsh is generating the same random value when calling a subshell.
Consider the following code:
$ cat script.sh
#!/bin/zsh
checkFieldConvergence () {
echo $RANDOM
echo $RANDOM
}
echo $(checkFieldConvergence)
echo $(checkFieldConvergence)
echo $(checkFieldConvergence)
checkFieldConvergence
checkFieldConvergence
checkFieldConvergence
$ ./script.sh
4049 24768
4049 24768
4049 24768
4049
24768
20764
3330
17114
1195
whereas the same with bash gives
$ cat script.sh
#!/bin/bash
checkFieldConvergence () {
echo $RANDOM
echo $RANDOM
}
echo $(checkFieldConvergence)
echo $(checkFieldConvergence)
echo $(checkFieldConvergence)
checkFieldConvergence
checkFieldConvergence
checkFieldConvergence
$ ./script.sh
12274 28155
27609 10269
14100 14662
6945
17897
20354
29817
14495
27552

why zsh is generating the same random value when calling a subshell
Bash detects if it's run in the subshell and if it is, bash re-seeds the random generator using the current value of gettimeofday(), while Zsh does not re-seed the generator and just calls rand().
References: https://github.com/bminor/bash/blob/f3a35a2d601a55f337f8ca02a541f8c033682247/variables.c#L1371 , https://github.com/bminor/bash/blob/f3a35a2d601a55f337f8ca02a541f8c033682247/lib/sh/random.c#L87 vs https://github.com/zsh-users/zsh/blob/00d20ed15e18f5af682f0daec140d6b8383c479a/Src/params.c#L4294 .
For a truly random number different between sub-shells, use SRANDOM.

Related

access modifiers in bash

Say I have a bash script and I want some variables to appear when sourced and others to only be accessible from within the script (both functions and variables). What's the convention to achieve this?
Let's say test.sh is your bash script.
What you can do is extract all the common items and put them in common.sh which can be sourced by other scripts.
The BASH_SOURCE array helps you here:
Consider this script, source.sh
#!/bin/bash
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
# this code is run when the script is _executed_
foo=bar
privFunc() { echo "running as a script"; }
main() {
privFunc
publicFunc
}
fi
# this code is run when script is executed or sourced
answer=42
publicFunc() { echo "Hello, world!"; }
echo "$0 - ${BASH_SOURCE[0]}"
[[ ${BASH_SOURCE[0]} == "$0" ]] && main
Running it:
$ bash source.sh
source.sh - source.sh
running as a script
Hello, world!
Sourcing it:
$ source source.sh
bash - source.sh
$ declare -p answer
declare -- answer="42"
$ declare -p foo
bash: declare: foo: not found
$ publicFunc
Hello, world!
$ privFunc
bash: privFunc: command not found
$ main
bash: main: command not found

Why does BASHPID change in here-strings

Can somebody explain the following behaviour with a here-string, please?
$ echo "$SHLVL $BASH_SUBSHELL $BASHPID $$"
1 0 18689 18689
$ cat <<< "$SHLVL $BASH_SUBSHELL $BASHPID $$"
1 0 19078 18689
$ cat <<< "$SHLVL $BASH_SUBSHELL $BASHPID $$"
1 0 19079 18689
The BASHPID is different from the shell PID in the here-string, and change each time, but I don't understand why.
The same happens in here-documents:
$ cat << EOT
> $SHLVL $BASH_SUBSHELL $BASHPID $$
> EOT
1 0 19096 18689
Surprisingly, BASHPID doesn't change in a command group:
$ { cat ;} <<< "$SHLVL $BASH_SUBSHELL $BASHPID $$"
1 0 18689 18689
On the other hand, it changes in a subshell
$ (echo $BASHPID ; cat) <<< "$SHLVL $BASH_SUBSHELL $BASHPID $$"
20465
1 1 20465 18689
while the here-string is supposed to expand in the current shell.
Note: my bash version is 4.3+
(Just guessing ...)
The behavior is similar to this:
# echo $$
35130
# echo $( echo $$ $BASHPID )
35130 88025
# echo $( echo $$ $BASHPID )
35130 88026
#
# # or
#
# echo $$ $BASHPID | cat
35130 88028
# echo $$ $BASHPID | cat
35130 88030
Apparently $BASHPID is not expanded at the same time as $$. According to man bash:
BASHPID
Expands to the process ID of the current bash process. This differs from $$ under certain circumstances, such as subshells that do not require bash to be re-initialized.
This implies $BASHPID is not expanded the time Bash parses the command line otherwise it'll be the same as $$. And in Bash source code there's a func initialize_dynamic_variables() (in file variables.c):
1905 static void
1906 initialize_dynamic_variables ()
1907 {
1908 SHELL_VAR *v;
1909
1910 v = init_seconds_var ();
1911
1912 INIT_DYNAMIC_VAR ("BASH_ARGV0", (char *)NULL, get_bash_argv0, assign_bash_argv0);
....
....
1924 INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign);
1925 VSETATTR (v, att_integer);
....
As it shows, vars like BASHPID are called dynamic variables. I guess these vars are handled specially and are expanded in the last minute when it knows that no more sub-shell will be forked (the fork may be followed by exec(), e.g., to run external commands).

How to share variable with sub-thread?

I would like to run a bash script with a watchdog function launched in sub thread that will stop my program when a given variable reach a value. This variable is incremented in the main thread.
var=0
function watchdog()
{
if [[ $var -ge 3 ]]; then
echo "Error"
fi
}
{ watchdog;} &
# main program loop
((var++))
The problem in this code is that $var stays at 0. I also tried without {} around the watchdog call, same result.
Is my code style good ?
You cannot share variables between processes in bash, and it does not support multi-threading. So you need a form of Inter-Process Communication. One of the simplest is to use a named pipe, also known as a FIFO.
Here is and example:
pipe='/tmp/mypipe'
mkfifo "$pipe"
var=0
# Your definition is not strictly correct (although it will work)
watchdog()
{
# Note the loop
while read var
do
if (( var >= 3 )) # a better way to do numeric comparisons
then
echo "Error $var"
else
echo "$var"
fi
sleep 2 # to prevent CPU hogging
done
}
watchdog < "$pipe" & # No need for a group
# main program loop - ??? I see no loop
((var++))
echo "$var" > "$pipe"
((var++))
echo "$var" > "$pipe"
((var++))
echo "$var" > "$pipe"
echo "waiting"
wait
rm "$pipe"
Example run:
$ bash gash.sh
1
waiting
2
Error 3
However I really don't see the point in using a separate process. Why not just call a function to test the value after each change?
if you run your bashscript with a . before, it will be use the same environment and can change existing variable. Look at this:
$ cat test.sh
#!/usr/bin/env bash
a=12
echo $a
$ a=1
$ echo $a
1
$ ./test.sh
12
$ echo $a
1
$ . ./test.sh
12
$ echo $a
12
After i run . ./test.sh the variable $a has been changed through the script.

Syntax for inlining return value of call

While you can inline output of a program as parameters
$ echo $(ls)
cpp python bash
or as a temporary file
$ echo <(ls)
/proc/self/fd/63
I wonder how you can inline the return value with a similar syntax, so that it echoes the return-value of ls that it works like this:
$ ls
$ echo $?
0
ls_retval=$(ls >/dev/null 2>&1; echo "$?")
If you want to encapsulate that:
# define a function...
retval_of() { "$#" >/dev/null 2>&1; echo "$?"; }
# and use it
ls_retval=$(retval_of ls)
As for "with a similar syntax", though -- the shell has the syntax that it has; there doesn't exist "retval substitution" (as of bash 4.4, or POSIX sh as standardized in POSIX Issue 7).

Why ksh disables stderr when subshell is executed?

The program:
function import {
set -x
# read NAME < <(/usr/bin/pwd)
NAME=$(/usr/bin/pwd)
echo 123 >&2
set +x
}
echo aaaaaaaaaaa
import
echo bbbbbbbbbbb
OUT=$( import 2>&1 )
echo "$OUT"
echo ccccccccccc
I hoped to have the output between 'aaa' and 'bbb' to be the same as in between 'bbb' and 'ccc'. But it is not the case with ksh:
aaaaaaaaaaa
+ /usr/bin/pwd
+ NAME=/home/neuron
+ echo 123
+ 1>& 2
123
bbbbbbbbbbb
+ /usr/bin/pwd
ccccccccccc
If I change $( ... ) into < <(...), stderr works as usual and I have the same output. I tried that on solaris and linux and it behaves the same, so I guess it's not ksh bug. Please note that it's not just 'set -x' being disabled, also the 'echo 123 1>&2' output disapears. In bash the code works as I would suppose.
My questions are 'why' and 'how to capture the function's stdout and stderr?'
Thank you
Vlad
It's confirmed to be a bug. And we can reproduce it very simply:
Something appears broken with nested command substitution:
for command in true /bin/true; do
a=$( ( b=$( $command ); echo 123 >& 3; ) 3>& 1 ) &&
echo a=$a command=$command
done
a=123 command=true
a= command=/bin/true
We run the same assignment twice, once with a builtin, and once with
an external command. We would expect the same results, but this fails
when we include the external command.
Glenn Fowler:
I believe this was fixed between 2012-08-23 and 2012-10-12
Tested with the latest beta:
$ ${ksh} test.sh
Version AIJM 93v- 2014-01-14
a=123 command=true
a=123 command=/bin/true
$
$ ksh test.sh
Version JM 93u 2011-02-08
a=123 command=true
a= command=/bin/true
$
This looks like a bug in ksh (My version is u), and it is not specific to pwd or stderr.
Below I can reproduce the effect with true and fd 3. (This allows us to keep use the shell trace)
The effect appears to be triggered by assigning output from an external process
into a variable inside a function. If the assignment is then followed by output
to some other file descriptor, that output gets lost.
The idea here is that the $( ... ) construct somehow conflicts with the subsequent redirect,
but only when the code in ... is run externally. The true and pwd builtins don't trigger a subshell in ksh93.
I will ask David Korn for confirmation.
function f1 {
var=$( /bin/true )
echo 123 >& 3
}
function f2 {
var=$( true )
echo 123 >& 3
}
function f3 {
typeset var
var=$( /bin/true )
echo 123 >& 3
}
functions="f1 f2 f3"
typeset -tf ${functions}
exec 3>& 1
echo ${.sh.version}
for f in ${functions}; do
echo TEST $f
functions $f
echo "123 expected: "
$f
OUT=$( $f 3>& 1 )
echo "OUT='123' expected"
echo "OUT='$OUT'" Captured output
echo
done
output:
Version JM 93u 2011-02-08
TEST f1
function f1 {
var=$( /bin/true )
echo 123 >& 3
}
123 expected:
123
OUT='123' expected
OUT='' Captured output
TEST f2
function f2 {
var=$( true )
echo 123 >& 3
}
123 expected:
123
OUT='123' expected
OUT='123' Captured output
TEST f3
function f3 {
typeset var
var=$( /bin/true )
echo 123 >& 3
}
123 expected:
123
OUT='123' expected
OUT='' Captured output

Resources