0

I am writing a shell function which makes an external API call via cURL (the external API syntax isn't under my control). I've approached it like this (simplified):

#!/bin/sh

template_get_entry='get_entry:%s'
template_set_entry='set_entry:%s=%s'

curlheaders='-H stuff'
curluri="https://www.domain.com:1234/api.php"

# make an API call to get entry "foo"
call_api "$template_get_entry" "foo"

# make an API call to set entry "foo" to "bar"
call_api "$template_set_entry" "foo" "bar"

call_api() {

  apicmd="$( printf "$1" "$2" "$3" )"
  result="$( eval "/usr/local/bin/curl" "$curlheaders" "-d" "$apicmd" "$curluri" )"
  retcode="$?"

  .....stuff.....

}

There are 2 problems with this code.

First, the number of args is variable. If the line defining apicmd is called with less than the maximal number of args, printf interprets any excess commands as extra instances of printing the format string, to be appended. I can't see the correct way to work around this.

Second, because I've used eval, this creates a knockon problem with eval, in that retcode will surely pick up the return code from eval and not from curl, and I don't know the right way to prevent/fix that.

How should I do something like this, which needs a variable number of args?

  • 1
    I don't really see why you use eval... – Kusalananda Aug 7 '18 at 7:32
  • I don't know another way or a more sensible way to do variable-argument-count substitution, and run the result as a command (yet). I might, by the end of this question. – Stilez Aug 7 '18 at 10:03
  • You don't seem to have a variable number of arguments in the call to curl though. – Kusalananda Aug 7 '18 at 10:06
  • I don't. The curl arg string is templated, though, and the args used to build it are passed as the functio's 2nd+ args, substituted in the functions 1st arg, and the resulting string is used as the curl args. That's where the variable args list kicks in. – Stilez Aug 7 '18 at 10:59
  • It's still just a simple string when you do the call to curl though. – Kusalananda Aug 7 '18 at 11:03
1

You could try to fill the format strings with zero length specifiers up to the maximum expected parameter count:

template_get_entry='get_entry:%s %0.0s'
  • Worked, and keeps code short. Note that if I didn't want to add null (zero length) field placeholders or didn't know the max args, I would have tried Kusalananda's solution. – Stilez Aug 8 '18 at 11:13
1

What you could do is to get the format string argument, shift it off the argument list and then use $@:

call_api () {
    fmt=$1
    shift

    apicmd=$( printf "$fmt" "$@" )

    # ...
}
  • This sounds like it would work; however I ended up using RudiC's solution because it keeps the code short and if it worked would almost certainly be guaranteed portable, whereas shell substitutions and builtins might not always be. But I appreciate this answer which would have been my #2 choice to try, if it hadn't. Also this would be better code if I wasn't sure of a maximum number of args, or didn't want to pad out the template strings with extra null fields. – Stilez Aug 8 '18 at 11:12

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.