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
  • 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 – Chris Davies 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
  • 138,973
  • 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

Chris Davies
  • 116,213
  • 16
  • 160
  • 287