How to use quotes in environment variables expansion - linux

In How can I use environment variables in body of a curl PUT request?, I was given the great advise to always use " using doing environment variables.
Say I want do following query:
curl -XPUT http://"${HOST}"/create/"${USER}" -d'{"user":"'"${USER}"'"}'
I enclosed ${USER} between " to ensure that spaces in the user name are possible. I did the same for ${HOST}, although that was strictly not required, since hostnames cannot contain spaces as far as I know.
I am wondering if the following request is equal to the previous request:
curl -XPUT "http://${HOST}/create/${USER}" -d'{"user":"'"${USER}"'"}'
Are they equal? Which one is preferred/most standard?

Yes, they are equal.
I'd prefer
curl -XPUT "http://${HOST}/create/${USER}" -d"{\"user\":\"${USER}\"}"
first because:
it is shorter as #Ryan said in comment
second literal is more readable when in one chunk rather than concatenatig two styles of quotes
some editors will highlight them in more readable way (for example vim )

As you've seen, dealing with quoting conventions in Bash when you have arbitrary data is difficult. However, there's a third way of quoting in cases like this than can make life a lot easier: "here documents".
Using <<TOKEN in a shell command indicates that the lines after the command will be read as the standard input to the command, terminated with TOKEN. Within the here document the usual quoting characters lose their special meaning and are interpreted literally, but variable substitution still happens normally.
To demonstrate, start a netcat "server" to display requests in one terminal with
nc -kl localhost 8888
Now, in another terminal, run this shell script:
name="Alice's Restaurant"
password="quote is ' and doublequote is \\\"."
curl -XPUT http://localhost:8888/create/user --data-binary #- <<EOF
{
"name": "$name",
"password": "$password",
"created": "$(date --iso-8601)"
}
EOF
When a --data argument is given # that requests that curl read the data from the filename specified immediately after the #, and using - as the filename reads from stdin.
Note that here I use --data-binary to make the server's output easier to understand; in production use you'd want to use --data-urlencode or, if the server accepts data in another format, ensure you're setting the Content-type header to that format rather than leaving it at the default application/x-www-form-urlencoded.
When you run the above, you'll see the following in your netcat terminal:
PUT /create/user HTTP/1.1
Host: localhost:8888
User-Agent: curl/7.52.1
Accept: */*
Content-Length: 112
Content-Type: application/x-www-form-urlencoded
{
"name": "Alice's Restaurant",
"password": "quote is ' and doublequote is \".",
"created": "2018-02-20"
}
As you can see, normal quoting characters are not treated specially, you need not do any special quoting on individual shell variables that get expanded within the here document, and you can even use $() to run shell commands whose output will be substituted within the document.
(By the way, I specified the double quote within the password variable as \\\", setting it to \" in the variable after shell interpolation of a double-quoted string, because that's necessary to produce valid JSON. Oh, you can never escape the quoting issues.)

Related

Escape semicolon, double quotes and backslashes for curl

What is the proper way to send this ,./'; '[]}{":?><|\\ as form-data value in curl. I'm doing this
curl --location --request POST 'https://postman-echo.com/post' \
--form 'more=",./'\'';[]}{\":?><|\\\\'"
right now and it gives different result, apparently only 2 backslashes in the response which is supposed to be 4 in total
Response snippet here
Solved!
Apparently this was the problem with my fish shell which was escaping the trailing double quotes. When i ran the same request on bash it was successful.
probably a shell is interpreting your cmd-line and each \\ pair gets reduced to a single \
(revision, even more explicit) :
using echo to show results,
echo more=",./';[]}{\":?><|\\\\\\\\"
adjust if needed, then copy the more="..." part to your curl cmd-line

What does <<$$$ mean in a Unix shell?

I'm using the google-http-client for a project at work and when I do some requests I have the following thing printed on my console.
curl -v --compressed -X POST -H 'Accept-Encoding: gzip' -H 'User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)' -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -d '#-' -- 'http://example.com' << $$$
I was wondering what << $$$ mean.
If I try to run this command into a linux terminal seems that << $$$ makes the console to wait for more input. If that's the case, how can I specify to the terminal that I'm done feeding inputs to it?
Later edit: I have found that curl arguments -d #- implies that data will be red from the stdin.
This is a "here-document" with an unusual end marker.
A here-document is a type of redirection, and usually looks like
utility <<MARKER
document
content
goes here
MARKER
That is, it feeds a document delimited by MARKER to the utility on its standard input.
This is like utility <file where file contains the lines in the here-document, except that the shell will do variable expansion and command substitution on the text of the document (this may be prevented by quoting the marker as either \MARKER or 'MARKER' at the start).
The here-document marker can be any word, but $$$ is a highly unusual choice of word for it. As $ has a special meaning in the shell, using $ in the marker is, or may be, confusing to the reader.
If you type
somecommand <<stuff
in the shell, the shell expects you to give the rest of the contents of the here-document, and then the word stuff on a line by itself. That's how you signal end of input in a here-document.

Multiword string as a curl option using Bash

I want to get some data from a HTTP server. What it sends me depends on what I put in a POST request.
What I put in the INPUT_TEXT field is a sequence of words. When I run the following command, I get good looking output.
$ curl http://localhost:59125/process -d INPUT_TEXT="here are some words"
I want a bash script to take some string as a command line argument, and pass it appropriately to curl. The first thing I tried was to put the following in a script:
sentence=$1
command="curl http://localhost:59125/process -d INPUT_TEXT=\"${sentence}\""
$command
I then run the script like so:
$ ./script "here are some words"
But then I get a curl Couldn't resolve host error for each of "are", "some", and "words". It would seem that "here" got correctly treated as the INPUT_TEXT, but the rest of the words were then considered to be hosts, and not part of the option.
So I tried:
command=("curl" "http://localhost:59125/process" "-d" "INPUT_TEXT='$sentence'")
${command[#]}
I got the same output as the first script. I finally got what I wanted with:
result=$(curl http://localhost:59125/process -d INPUT_TEXT="${sentence}")
echo $result
I'm still unsure as to what the distinction is. In the first two cases, when I echoed out the contents of command, I get exactly what I input from the interactive Bash prompt, which had worked fine. What caused the difference?
The following will work:
command=("curl" "http://localhost:59125/process"
"-d" "INPUT_TEXT=$sentence")
"${command[#]}"
That has two changes from yours:
I removed the incorrect quotes around $sentence since you don't want to send quotes to the server (as far as I can see).
I put double-quotes around the use of "${command[#]}". Without the double quotes, the array's elements are concatenated with spaces between them and then the result is word-split. With double quotes, the individual array elements are used as individual words.
The second point is well-explained in the bash FAQ and a bunch of SO answers dealing with quotes.
The important thing to understand is that quotes only quote when a command is parsed. A quote which is a character in a variable is just a character; it is not reinterpreted when the value of the variable expanded. Whitespace in the variable is used for word-splitting if the variable expansion is unquoted; the fact that the whitespace was quoted in the the command which defined the variable is completely irrelevant. In this sense, bash is just the same as any other programming language.

Sending a post json with curl get error

Im trying to create a bash script to make github pull requests here its my code:
Gist Code
I get this error:
curl: (6) Could not resolve host: on
curl: (3) [globbing] unmatched close brace/bracket in column 63
{
"message": "Problems parsing JSON",
"documentation_url": "https://developer.github.com/v3"
}
Please help
More/proper quoting:
body=$(printf '{"title":"%s","body":"%s","head":"clamour:%s","base":"%s"}' "$TITLE" "$DESCRIPTION" "$TARGET" "$SOURCE")
curl -H "$auth" -d "$body" "https://api.github.com/repos/clamour/$PROJECT/pulls"
All user-supplied variables must be quoted (unless you know exactly why you want to leave then unquoted).
Enclosing a variable name in ${braces} is not the same as "$quoting".
I find using printf tends to be more clear than mixing double and single quotes with variable interpolation.
Get out of the habit of using ALLCAPS variable names: one day you'll use PATH=... and then wonder why your script is broken
you are using a lot of variables
try to do it like this
BODY="{\"title\":\"$TITLE\",\"body\":\"$DESCRIPTION\",\"head\":\"clamour\":\"$TARGET\",\"base\":\"$SOURCE\"}"
"https://api.github.com/repos/clamour/$PROJECT/pulls"
read also this Difference between single and double quotes in Bash

Passing quotes and other special characters literally through bash and ssh

I am trying to run an SSH command that will invoke a script on a remote machine that writes some Lua code to a file.
I have this script command that executes under bash:
ssh bob writelua.sh '{version=1,{["foo"]=17}}'
And writelua.sh looks like this:
echo "return $1" > bar.lua
The end result, however, is that bar.lua has the content:
return version=1
I had thought that single quotes prevented all interpretation. How can I edit the scripts and escaping to pass the raw Lua code through unharmed?
The single quotes prevent interpretation on the local host. The remote host sees the command line
writelua.sh {version=1,{["foo"]=17}}
which is subject to brace expansion. You need a second set of quotes so that the first set of single quotes is passed through to the remote host.
ssh bob writelua.sh "'{version=1,{[\"foo\"]=17}}'"
As you can see, the quotes start to get unwieldy. A better solution is to simply copy a script containing
writelua.sh '{version=1,{["foo"]=17}}'
to the remote host and execute that remotely.
An example using the $'...' quotes:
ssh bob writelua.sh $'{version=1,{[\'foo\']=17}}'
Use heredoc and avoid all the excessive quoting:
ssh -T bob << \EOF
writelua.sh '{version=1,{["foo"]=17}}'
EOF
This will send raw script to remote host and it will get interpreted on the remote host itself.
When it gets too complex, particularly with lots of escaping, I prefer generating the command on a temporary script and execute it locally or remotely via SSH as required.
But there's an alternative: using echo to store the command in a variable and taking advantage of three things:
Single quotes don't do variable expansion and allow double quotes, so you can include something like "$myvar" without escaping $ or "
Double quotes allow variable expansion and single quotes, which means you can include something like animals='all'; echo love $animals to have $animals replaced by its value, and without escaping the '
Strings of both types, i.e. enclosed by single quotes or double quotes, can be concatenated simply by putting them together.
As an example, if I want something like this executed on a remote machine:
source /my-env.sh; perl -MMYLIB::DB -e 'my $t=db_list("name", 1553786458); print "#$t"'
But instead of 1553786458 I want to pass the value from a local variable:
now=`date +%s`
We could have this:
get_list=`echo 'source /my-env.sh; perl -MMYLIB::DB -e' "'my " '$t=db_list("name", ' "$now" '); print "#$t"' "'"`
You can see that single and double quotes are alternated, so we din't have to do any escaping! They don't need to be separated by spaces, but it improves readability and won't affect the result in this case.
And now we can execute:
ssh user#host $get_list
There's still no guarantee that this approach will always work, so once you've built your command, the safest bet would be to copy it over in a file.
If you can use Perl...
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new("bob");
$ssh->system('writelua.sh', '{version=1,{["foo"]=17}}')
or die $ssh->error;
Net::OpenSSH takes care of quoting everything for you.

Resources