1

I'm writing a bash script that kicks off some simulations, and then makes a bunch of plots based on the outputs generated by those simulations.

I want to run the simulations in parallel, but need the script to "block" waiting for all simulations to complete before moving on to the plot generating scripts.

pids=()

for file in inputdir/*
do
    ./run_simulation $file &> /dev/null & pids+=($!)
done

# synchronization barrier
for pid in ${pids[*]}; do
    wait $pid
done

The current script works just fine, I'm just generally curious: what is the difference in using ${pids[*]} when compared to ${pids[@]}? (My script uses the former).

alex
  • 65
  • 1
  • 6
  • 1
    Not an exact duplicate, but [this question](https://stackoverflow.com/questions/2761723/what-is-the-difference-between-and-in-shell-scripts) explains the difference between `$*` and `$@`; the same applies to arrays. – Benjamin W. Feb 13 '19 at 23:17
  • 1
    Consider running your code through http://shellcheck.net/ and following its advice, btw -- it'll advise you to quote `$file` as `"$file"`, f/e. I would also tend to quote `pids+=( "$!" )` and use `wait "$pid"` as a matter of best-practice/paranoia (as *not* quoting an expansion tells the shell to do extra operations on it post-expansion; there's no point in asking the shell to string-split and glob-expand values you don't *want* to have those operations take place on). – Charles Duffy Feb 13 '19 at 23:25
  • 1
    Also, note that `${pids[@]}` without quotes is *exactly the same* as `${pids[*]}`; you need to add the quotes, making it `"${pids[@]}"`, for there to be any difference whatsoever. – Charles Duffy Feb 13 '19 at 23:27

1 Answers1

4

In practice, it doesn't matter if you're certain that IFS will only contain a reasonably sane value. (If it contained numeric characters which could exist inside a PID, then your values could be corrupted by the extra operations ${pids[*]} goes through).

However, "${pids[@]}" is the Right Thing, insofar as its semantics are what you want: Expanding each item in the array to a single word.

When you run ${pids[*]}, the shell expands the list into a single string, separating subsequent elements with the first character inIFS (by default, a space). It then subjects that result to string-splitting (splitting it back from a single string to a list of separate items), and expands each result of that split operation to a glob.


Here's a concrete example:

pids=( 123 456 789 012 )
IFS=02468 # now entering Insanity World!
echo "${pids[@]}"  # still works correctly
echo ${pids[*]}    # not so much

...will emit as its two lines of output (as seen at https://ideone.com/mqtEfQ):

123 456 789 012
1 3  5  7 9  1

...as the digits present in IFS are used to split into separate echo arguments, and not passed as literal data.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441