1

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
  • 1
    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 gnu.org/software/bash/manual/bash.html#Looping-Constructs – glenn jackman Sep 26 '18 at 13:57
6

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}")

  • 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 8:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.