0

I'm running offlineimap on a bunch of accounts and want to check the exit code of each run and perform some actions accordingly.

I have 6 separate email accounts that I run against with a lot of code duplication. The original command structure is:

$ $(which offlineimap) -c offlineimaprc -o -a yahoo & declare yahoo_pid=$!
wait $yahoo_pid
yahoo_st=$?
if [[ $yahoo_st -ne 0 ]];then <do some stuff>

$ $(which offlineimap) -c offlineimaprc -o -a gmail & declare gmail_pid=$!
wait $gmail_pid
gmail_st=$?
if [[ $gmail_st -ne 0 ]];then <do some stuff>

Now I'd like to remove the duplication and run this from a for in loop and the wait command. The ${account-name}_pid (e.g. yahoo_pid) substitution works fine but I get stuck with the wait command.

$ for app in yahoo gmail 
  do 
   $(which offlineimap) -c offlineimaprc -o -a ${app} & declare ${app/%/_pid}=$!
   wait ${app}_pid
  done
[1] 73443
-bash: wait: `$yahoo_pid': not a pid or valid job spec
[2] 73444
-bash: wait: `$gmail_pid': not a pid or valid job spec
Tony Barganski
  • 370
  • 4
  • 9
  • 1
    You've got part of the way by using arrays; now look up *associative* arrays and you'll be able to cleanly solve this problem – muru May 24 '22 at 14:51
  • @muru In the spirit of this site, can you give some examples of how you might solve this problem? Nothing I have read and tried seems to work. The `$yahoo_pid` value is assigned in the statement just before the `wait` command but I can't seem to access it. – Tony Barganski May 24 '22 at 20:30
  • 1
    @TonyBarganski I've moved your answer to... an answer. If you want the points please write your own - and accept it - and we'll delete the community answer – roaima May 24 '22 at 22:36
  • TonyBarganski in the spirit of this site, I'll just note that the answers here are essentially identical to the duplicate, including yours. But @ilkkachu's option using associative arrays is what I meant. – muru May 24 '22 at 23:08

2 Answers2

2
app=(yahoo gmail)
# ...
declare ${app/%/_pid}=$!

This, above, looks odd. While app is an array, just referencing $app will get the first element from the array, same as ${arr[0]}. So that would always assign to yahoo_pid.

$(eval echo \$${app}_st)=$?

This won't work at all. Having an expansion on the left side of the equals sign makes it an invalid assignment, and the shell will process it as a command. If $yahoo_st is not set, and $? is e.g. 0, it'll try to run a command called =0.


Anyway, to your case.

Bash has associative arrays, i.e. arrays indexed by strings, and they're pretty much what you need here. They need to be declared with declare -A, regular arrays can just be assigned as you did above.

imap=$(which offlineimap)
apps=(yahoo gmail)
declare -A pids=()
declare -A st=()

for app in "${apps[@]}"; do
    "$imap" -c offlineimaprc -o -a "$app" &
    pids[$app]=$!
    wait "${pids[$app]}"
    st[$app]=$?
done

# and to check on them later:

for app in "${apps[@]}"; do
    printf "app '%s' ran pid %s returning status %s\n" "$app" "${pids[$app]}" "${st[$app]}"
done

or maybe:

for app in "${apps[@]}"; do
    "$imap" -c offlineimaprc -o -a "$app" &
    pids[$app]=$!
done
for app in "${apps[@]}"; do
    wait "${pids[$app]}"
    st[$app]=$?
done
# ...

Alternatively, you could use namerefs to refer to a variable named in another. This would set pid_yahoo and st_yahoo:

app=yahoo
declare -n pid="pid_$app"
declare -n st="st_$app"
something... & pid=$!
wait "$pid"
st=$?

But really just use associative arrays.

See also e.g.

ilkkachu
  • 133,243
  • 15
  • 236
  • 397
  • Wonderful! I have only one other criteria that I probably didn't make too clear - parallel execution. With having the `wait` command in the launch loop, the mail fetching is serialised and slow. Any suggestions on where best to run these outside of the launch loop? – Tony Barganski May 25 '22 at 09:21
  • 1
    @TonyBarganski, yeah, I just copied the structure from your question to the first example. But I think it should work if you move the `wait` calls to another loop, as in what I had there under the "or maybe". I didn't extensively test the whole thing, though. – ilkkachu May 25 '22 at 09:35
  • 1
    I moved it to an identical `for app in` loop just below the launch loop and it works perfectly! :) – Tony Barganski May 25 '22 at 09:38
  • 1
    @TonyBarganski, ...the other thing I should have said is that for things like this, you may want to use dedicated tools, like GNU Parallel, see https://unix.stackexchange.com/tags/gnu-parallel/info and https://www.gnu.org/software/parallel/ and all the questions about it on the site https://unix.stackexchange.com/tags/gnu-parallel/ – ilkkachu May 25 '22 at 15:07
  • The question for me always comes down to, “Job Status”. Seems that GNU Parallel does have a `—joblog` option which outputs the exit code for each job run so it would be trivial to use awk and retrieve the exit codes. One for another day. :) – Tony Barganski May 25 '22 at 18:38
-1

Moved from question to answer

It took a lot of searching and examples for the penny to finally drop on this one but here's what ended up working for me:

app=(yahoo gmail)

$(which offlineimap) -c offlineimaprc -o -a ${app} & declare ${app/%/_pid}=$!

wait $(eval echo \$${app}_pid) <<== THIS IS THE MAGIC SYNTAX

$(eval echo \$${app}_st)=$?  && if [[ $(eval echo \$${app}_st) -ne 0 ]];then error_msg ${app}; fi
roaima
  • 107,089
  • 14
  • 139
  • 261