3

I have this script to generate $1 digit(s) of mixed random char from the defined charlists as arrays.

#!/bin/bash

charlist1=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
charlist2=(0 1 2 3 4 5 6 7 8 9)
charlists=(${charlist1[*]} ${charlist2[*]})

i="1"
while [ $i -le $1 ]; do
    out=$out`echo -n ${charlists[$(($RANDOM % ${#charlists[*]}))]}`
    i=$(( i + 1 ))
done

echo $out

It runs fine in bash, but then if I invoke it with zsh by zsh that_script_above.sh 6 it just generates 6 digit of some same character like this:

>>> zsh that_script_above.sh 6
llllll
>>> zsh that_script_above.sh 6
bbbbbb

And if I modified that script to be like this:

#!/bin/bash

charlist1=(a b c d e f g h i j k l m n o p q r s t u v w x y z)
charlist2=(0 1 2 3 4 5 6 7 8 9)
charlists=(${charlist1[*]} ${charlist2[*]})

i="1"
while [ $i -le $1 ]; do

    echo -n ${charlists[$(($RANDOM % ${#charlists[*]}))]}

    i=$(( i + 1 ))
done

echo

it works well as I desired for both bash and zsh.

So, here is my questions:

  1. Can someone explain to me about my problem with zsh bash behaviour stated above?
  2. And how can I use for loop in bash with customizable variable? because it seems for i in {1..$1} doesn't work in bash.
  • 2
    Beware bash/zsh/ksh pseudo random number generators are not cryptographically secure, they shouldn't be used to generate secrets. – Stéphane Chazelas Sep 26 '18 at 10:21
  • @StéphaneChazelas actually it is just to generate unique folder name, by iterating it and comparing with array generated by command ls in some folder, i can get unique id... – user312781 Sep 26 '18 at 11:35
  • 2
    Many systems have a mktemp command just for that: dir=$(mktemp -dp . myprefixXXXXXX) would create a (private) unique directory in the current directory with that template and store it in $dir. Comparing with ls is racy. It's better to attempt the mkdirs until it succeeds. Beware of symlinks as well! All those issues are handled properly by mktemp. – Stéphane Chazelas Sep 26 '18 at 12:03
  • Whoaaa that's new... never thought about symlinks... thanks pal – user312781 Sep 26 '18 at 12:47
  • You could use a "C-style" for loop in bash: for ((i=1; i<="$1"; i++)); do ... -- see https://www.gnu.org/software/bash/manual/bash.html#Looping-Constructs – glenn jackman Sep 26 '18 at 13:57

1 Answers1

10

Q1. Command substitution (backticks) uses a subshell, and in zsh a subshell's RNG state not reseeded. Since you repeatedly create a new subshell without using $RANDOM in the parent, you get the same value in each subshell. See:
https://stackoverflow.com/questions/32577117/references-to-random-in-subshells-all-returning-identical-values
https://superuser.com/questions/1210435/different-behavior-of-in-zsh-and-bash-functions

You don't need the command-substitution and echo, and you also don't need the $((..)) because an array subscript is already evaluated as an arithmetic expression, but you do need +1 because zsh arrays are 1-origin (you were lucky you didn't happen to hit 0):

 out=$out${charlists[ $RANDOM % ${#charlists[*]} + 1 ]}

Aside: even if you did need an echo in command substitution you don't need -n because command substitution itself removes any trailing newline(s) from the data captured and substituted.

Q2. bash does brace-expansion before it does parameter-expansion (and command and arithmetic substitution/expansions), but zsh (and ksh) does it after. You can use for i in $(seq 1 $1) or yucky but builtin for i in $(eval "echo {1..$n}")

  • 1
    Whooa thanks so much. Sorry if I asked question that apparently had already been asked. I've googling with irrelevant keyword, thanks for referring the link. And the fact that command in backticks forking a subshell is so new to me, probably the best thin I learn today. But what about $(eval (echo))? Does it uses subshell? – user312781 Sep 26 '18 at 08:49
  • Yes. $(...) is a subshell. <(...) is a subshell. All of the commands in a pipeline except the first or last are in subshells. (In bash, command | while read; do runs the while loop in a subshell, so it can have no lasting effects. ksh and zsh instead run the while in the current shell, so command is the one that can't have lasting effects.) – Mark Reed Jan 17 '22 at 04:01
  • @MarkReed: subshells for a pipeline is not relevant to this Q, but we have lots of other Qs or As on them. All nonlast parts are always subshells, and in bash the last part is sometimes not a subshell. See to start https://unix.stackexchange.com/questions/677519/ https://unix.stackexchange.com/questions/440088/ https://unix.stackexchange.com/questions/442215/ https://unix.stackexchange.com/questions/143958/ https://unix.stackexchange.com/questions/172541/ https://unix.stackexchange.com/questions/9954/ https://stackoverflow.com/questions/2746553/ https://stackoverflow.com/questions/14686872/ – dave_thompson_085 Jan 26 '22 at 06:40