9

If I do gshuf -e $(seq 1 10) in bash it will print the numbers 1 thru 10 in random order.

But if I do:

a=$(shuf -e $(seq 1 10))
for i in "${a[@]}"
do
    echo $i
    echo "next"
done

It prints all ten numbers followed by "next".

How do I loop over the output from shuf (or gshuf in os x)? The variable, a, seems to be a string, so I could split it. But that seems sort of sloppy. How do I get shuf to output an array?

2
  • Is the output of gshuf on a single line, or multiple lines?
    – John
    Mar 9, 2015 at 13:43
  • 1
    Just use for i in $a
    – Drav Sloan
    Mar 9, 2015 at 13:47

5 Answers 5

11

You are using a scalar assignment. Either use an array

a=( $(shuf -e $(seq 1 10)) )
for i in "${a[@]}"
do
    echo $i
    echo "next"
done

or let the shell split the scalar

a=$(shuf -e $(seq 1 10))
for i in ${a}
do
    echo $i
    echo "next"
done
4
  • 1
    Either way, that assumes you've not modified $IFS, that is, that it still contains the newline character and doesn't contain decimal digits. Mar 9, 2015 at 14:00
  • 1
    Stephane, in the first place I assume that the context is as shown. If there's critical code around the sample code the poster will certainly file a new question with appropriate new context.
    – Janis
    Mar 9, 2015 at 14:06
  • 1
    I'm just point out. The split+glob operator behaviour depends on the content of that variable, so it's worth point it out when recommending its usage for anyone stumbling on the answer. Mar 9, 2015 at 14:13
  • @Janis did not realize that scalar and array assignments had different syntax in bash. thanks a lot
    – bernie2436
    Mar 9, 2015 at 16:34
8

you don't need to store the output in a variable:

seq 10 | shuf | while read i; do echo $i; done

That runs the while loop in a subshell, so the i variable cannot persist into the current shell. You can shopt -s lastpipe to counter that, or avoid piping altogether

while read i; do echo $i; done < <( shuf -e $(seq 10) )
2

If you want to process $a as an array you need to initialise it as an array:

a=( $(shuf -e $(seq 1 10)) )

Then your loop produces the expected output.

1
  • On one hand, this works fine for the use case present. On the other hand, it doesn't work in many other use cases -- has all the same bugs as foo=( $bar ); undesired glob expansion, unexpected string-splitting behavior, etc. Much better to use readarray -t a < <(seq 1 10 | shuf) or IFS=$'\n' read -r -d '' -a a < <(seq 1 10 | shuf) or a=( ); while read -r; do a+=( "$REPLY" ); done < <(seq 1 10 | shuf) if one wants to write code that'll do the normal/expected thing with more antagonistic inputs. Mar 9, 2015 at 22:09
2

Just loop over output of shuf, you don't need variable here:

for i in $(shuf -e {1..10}); do
  echo "$i"
  echo "next"
done
1
  • Well unless $a has re-use later :) In which case for i in $a would suffice
    – Drav Sloan
    Mar 9, 2015 at 13:47
1

a one-liner...

shuf -e {1..10} | xargs -I%    echo % next
3
  • I see three commands: shuf, xargs and echo
    – Anthon
    Mar 9, 2015 at 15:16
  • @anthon, I meant "loop over the output from shuf" and execute just one command. Probably I should rephrase it (my English is awful).
    – JJoao
    Mar 9, 2015 at 15:29
  • 1
    I would call it a one-liner. But if it solves the problem the name, nor the number of commands is important.
    – Anthon
    Mar 9, 2015 at 15:34

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .