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.
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
user312781
  • 51
  • 3
  • 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}")

dave_thompson_085
  • 3,790
  • 1
  • 16
  • 16
  • 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