I tuck variable assignments in front of commands in the terminal all the time. Mostly to prevent them from sticking around the shell after running commmands. They appear like environment variables to the process that follows them. I also setopt HIST_IGNORE_SPACE
in ~/.zshrc
so that commands I choose to prefix with a space will not be saved to the history. Useful if you need to set a token or something.
So, today I did this.
TOKEN=asdf curl http://localhost:3000 \
-H "Authorization: bearer $TOKEN"
In an attempt to prevent needing to export
it in my shell (so it sticks around), and to prevent it from appearing in the zsh history.
Test it by starting a local server, e.g. with netcat
nc -l 3000
Then run the curl command.
GET / HTTP/1.1
Host: localhost:3000
User-Agent: curl/7.85.0
Accept: */*
Authorization: bearer
To my surprise the token was not present. At first I thought it might be the \
, used to wrap long lines for readability. But as an even simpler example, this also does not work
TOKEN=asdf echo $TOKEN
Why?
This is because the shell first expands $TOKEN
, that has yet to be set. Hence, it expands to the empty string ""
. This happens before the command is run, so what actually ends up running is this
TOKEN=asdf echo
No wonder there’s no token.
There are lots of ways to solve this, here’s a few
Solution 1: A program reads the variable
I guess I’m usually in luck that this is the case for me, which is probably why I expected the initial curl command to work.
When some program reads the environment variable, e.g. if this was the contents of test.sh
#!/bin/bash
echo $TOKEN
Make your user able to run it
chmod u+x test.sh
The following works as you’d expect
TOKEN=123 ./test.sh
123
Solution 2: Pass the literal string to bash -c
Use single quotes, i.e. '
, to pass the literal string, including the (not expanded) $TOKEN
to bash, using the -c
option to make it read the command from a string.
TOKEN=asdf bash -c '
curl http://localhost:3000 \
-H "Authorization: bearer $TOKEN"'
GET / HTTP/1.1
Host: localhost:3000
User-Agent: curl/7.85.0
Accept: */*
Authorization: bearer asdf
Note that using double quotes, i.e. "
, will not work, as $TOKEN
again is expanded before the command runs.
Solution 3: Export the variable in a subshell
By exporting the token, but inside a subshell, by wrapping the command in parens, the token is visible to all programs that are forked inside the subshell. Once the subshell (the command in parens) is finished, the TOKEN
is again gone.
(export TOKEN=asdf;
curl http://localhost:3000 \
-H "Authorization: bearer $TOKEN")
GET / HTTP/1.1
Host: localhost:3000
User-Agent: curl/7.85.0
Accept: */*
Authorization: bearer asdf
Solution 4: But I use zsh
Yeah, me too. In that case, the following also works.
(TOKEN=asdf;
curl http://localhost:3000 \
-H "Authorization: bearer $TOKEN")
GET / HTTP/1.1
Host: localhost:3000
User-Agent: curl/7.85.0
Accept: */*
Authorization: bearer asdf