cURL / Mailing Lists / curl-users / Single Mail

curl-users

Re: Submitting form data breaks on various occasions

From: Brian Dessent <brian_at_dessent.net>
Date: Mon, 24 Mar 2008 19:22:49 -0700

MK wrote:

> speaking of special shell characters and quoting strings and such, actually I
> also have another problem which I couldn't solve yet also kinda related to this,
> maybe you know the solution here as well:
>
> I want to pass data arguments to curl using a bash variable instead of supplying
> them explicitly. So I defined a variable, like this:
>
> DOCDATA='-F "title=Hello World!" -F "body=Content goes here."'
>
> however, when I now invoke curl with that variable as an argument, like this:
> curl (url) $DOCDATA
>
> then I will get curl errors:
>
> Connection #0 to host localhost left intact
> * gethostbyname(2) failed for World!"
> * Couldn't resolve host 'World!"'
> * Closing connection #1

This has everything to do with how shell quoting works and nothing to do
with curl. Here's a simplified example that uses a C program "showargs"
that does nothing but print out its argv[]. (I find this program very
useful to keep around because if you have a problematic command you can
simply copy/paste or uparrow and insert 'showargs' at the beginning of
it.)

$ x='a b "c d"'

$ echo $x
a b "c d"

$ showargs $x
argv[0] = 'showargs'
argv[1] = 'a'
argv[2] = 'b'
argv[3] = '"c'
argv[4] = 'd"'

As you can see when the shell expands $x it does not see a string
containing three words, it sees four -- there are simply four things
separated by spaces (or more precisely, IFS.) The shell doesn't
consider quotes as meaningful in this context, so the fact that there
are double quotes inside x doesn't matter.

If you want to force the shell to evaluate x as an expression instead of
simply expanding it into words, then you have to use eval:

$ eval showargs $x
argv[0] = 'showargs'
argv[1] = 'a'
argv[2] = 'b'
argv[3] = 'c d'

But this is a dangerous solution to rely on, because if your "Hello
world" was actually "I have $4 in my pocket", it would be mangled
because now you're telling the shell to not just split words but to
evaluate the string (as if you'd typed it), and it would see $4 as a
variable and try to expand it -- most likely into nothing. So you'd
have to make sure that all of the metacharacters in the text of your
variables are quoted!

This can get very ugly, because essentially what you're doing is
combining multiple words into one word, only to turn right around and
parse them back out into multiple words -- and all the quoting has to be
just right to make that roundtrip correct. It's much simpler just to
use one variable where you intend one word. For example I would have
written:

title='Hello World!'
body="Content goes here."
url="http://foo/bar"
...
curl "$url" -F title="$title" -F body="$body"

Note that when you use a variable that might contain spaces and you
*don't* want it to be split into words, you need to quote it as "$foo".
This allows foo to have spaces in it but still only represent one word,
which is what you want:

$ showargs curl "$url" -F title="$title" -F body="$body"
argv[0] = 'showargs'
argv[1] = 'curl'
argv[2] = 'http://foo/bar'
argv[3] = '-F'
argv[4] = 'title=Hello World!'
argv[5] = '-F'
argv[6] = 'body=Content goes here.'

> The odd thing is, when I echo $DOCDATA and copy the output to curl as a direct
> argument, then it works!

Context matters. The shell treats strings differently depending on what
it's doing.

Brian
Received on 2008-03-25