0

I'm trying to make a call to an API and want to set some curl defaults into a single variable so when I make multiple calls, it uses the same set of 'defaults'.

For some reason, curl is not recognizing the -H 'Content-type: application/json' and I'm baffled as to why not.

opts=" -v -H 'Content-type: application/json' "
curl $opts -d '{"hi":1}' https://google.com

The above prints out

$ opts=" -v -H 'Content-type: application/json' "
$ curl $opts -d '{"hi":1}' https://google.com
* Could not resolve host: application                <-------- !!
* Closing connection 0
curl: (6) Could not resolve host: application
* Rebuilt URL to: https://google.com/
*   Trying 172.217.4.174...
* TCP_NODELAY set
* Connected to google.com (172.217.4.174) port 443 (#1)
* TLS 1.2 connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate: *.google.com
* Server certificate: Google Internet Authority G2
* Server certificate: GeoTrust Global CA
> POST / HTTP/1.1
> Host: google.com
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 8
> Content-Type: application/x-www-form-urlencoded    <-------- !!
>
* upload completely sent off: 8 out of 8 bytes

I marked 2 lines where there are issues. Curl is thinking that application/json is a host/path and it's not overriding the Content-Type.

However, this is fine:

$ curl -v -H 'Content-type: application/json' -d '{"hi":1}' https://google.com
* Rebuilt URL to: https://google.com/
*   Trying 172.217.12.46...
* TCP_NODELAY set
*   Trying 2607:f8b0:4000:813::200e...
* TCP_NODELAY set
* Immediate connect fail for 2607:f8b0:4000:813::200e: No route to host
* Connected to google.com (172.217.12.46) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate: *.google.com
* Server certificate: Google Internet Authority G2
* Server certificate: GeoTrust Global CA
> POST / HTTP/1.1
> Host: google.com
> User-Agent: curl/7.54.0
> Accept: */*
> Content-type: application/json                     <---------- yay
> Content-Length: 8
>
* upload completely sent off: 8 out of 8 bytes

So how can I get curl to recognize the -H parameter the way I want? And why is it behaving like this?

(yes, I know I'm using Google as my API endpoint and getting 4xx codes.. its just a webserver I can test against right now to make my point)

0

Short answer: see BashFAQ #50: "I'm trying to put a command in a variable, but the complex cases always fail!".

Long answer: Putting commands (or parts of commands) into variables and then getting them back out intact is complicated. The basic problem you're running into is that the shell parses quotes before expanding variable references, so putting quotes in a variable doesn't do anything useful -- by the time they're part of the command line, it's too late for them to have their intended effect.

There are a couple of ways to do this; which is best will depend on exactly what you're trying to do.

Option 1 is to use an array instead of a plain text variable:

opts=(-v -H 'Content-type: application/json')
curl "${opts[@]}" -d '{"hi":1}' https://google.com

The syntax is a bit messy (all of the quotes, parentheses, brackets etc I used above are actually necessary for it to work right), and arrays are a bash feature (not available in all shells, so be sure use a shebang like #!/bin/bash or #!/usr/bin/env bash). On the other hand, you can build the option array dynamically if you need to:

opts=(-v -H 'Content-type: application/json')
if [[ -n "$fakeuseragent" ]]; then
    opts+=(-A "$fakeuseragent")
fi
curl "${opts[@]}" -d '{"hi":1}' https://google.com

Option 2 is to use a function to wrap curl, and add your custom options:

mycurl() {
    curl -v -H 'Content-type: application/json' "$@"
}
mycurl -d '{"hi":1}' https://google.com
  • I had considered doing option2 but not option1. Your comment about the quotes being parsed before expanding the variable is where I didnt understand what bash was doing. Thanks! – Nate Fox Sep 26 '17 at 12:30
1

Maybe you could use the eval command here :

#!/bin/bash

opts=" -v -H 'Content-type: application/json' "

eval curl $opts -d '{"hi":1}' https://google.com

eval - construct command by concatenating arguments

The eval utility shall construct a command by concatenating arguments together, separating each with a space. The constructed command shall be read and executed by the shell.

http://www.unix.com/man-page/posix/1posix/eval/


The caveat with the former curl $opts is that the shell interprets all elements between spaces as separated arguments, like so :

curl -v -H "'Content-type:" "application/json'"

This is of course not what you want. eval evaluates the string $opts correctly.

  • Please do not use or recommend eval for things like this. It blurs the line between code and data (even further than it usually is in the shell), so you run the risk of something you thought was just data (e.g. a URL) suddenly start executing like a command. – Gordon Davisson Sep 26 '17 at 6:51

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.