Reputation: 185
To show what I am trying to do, this is part of the bash script I have so far:
COMMAND="${@:1}"
CONTAINER_DOCKER_NAME=this-value-is-computed-prior
MY_IP=this-ip-is-computed-prior
ssh user@$MY_IP -t 'bash -c "docker exec -it $( docker ps -a -q -f name='$CONTAINER_DOCKER_NAME' | head -n 1 ) /bin/sh -c "eval $(echo export FOO=$BAR) && $COMMAND""'
So let's break down the long command:
I am ssh-ing into a host where I run a bash which fetches the correct container with docker ps
and then I do docker exec
to run a shell in the container to load some environment variables that my $COMMAND
needs to work. Important to note is that $BAR
should be the value of the BAR
variable inside the container.
So thats what I'm trying to accomplish in theory. However when running this no matter how I set the braces, quotes or escape characters - I always run into problems, either the shell syntax is not correct or it does not run the correct command (especially when the command has multiple arguments) or it loads $BAR
value from my local desktop or the remote host but not from the container.
Is this even possible at all with a single shell one-liner?
Upvotes: 0
Views: 690
Reputation: 185
thanks to larsks great explanation I got it working, my final one-liner is:
ssh -i $ECS_SSH_KEY ec2-user@$EC2_IP -t "bash -c \"docker exec -it \$( docker ps -a -q -f name=$CONTAINER_DOCKER_NAME | head -n 1 ) /bin/sh -c \\\"eval \\\\\\\$(AWS_ENV_PATH=/\\\\\\\$ENVIRONMENT /bin/aws-env) && $COMMAND\\\"\""
so basically you wrap everything in double quotes and then also use double quotes inside of it because we need some variables ,like $DOCKER_CONTAINER_NAME
from the host. to escape the quotes and the $ sign you use \ .
but because we have multiple levels of shells (host, server, container) we also need to use multiple levels of escaping. so the first level is just \$ which will protect that the variable (or the shell command, like docker ps
) is not run on the host but on the server.
then the next level of escaping is 7 times \ . every \ escapes the character to the right so in the end it is \\\$
on the second level (server) and \$
on the third level (container). this ensures that the variable is evaluated in the container not on the server.
same principle with the double quotes. Everything between \"
is run on the second level and everything between \\\"
is run on the third level.
Upvotes: 0
Reputation: 312858
I think we can simplify your command quite a bit.
First, there's no need to use eval
here, and you don't need the &&
operator, either:
/bin/sh -c "eval $(echo export FOO=$BAR) && $COMMAND"
Instead:
/bin/sh -c "FOO=$BAR $COMMAND"
That sets the environment variable FOO
for the duration of
$COMMAND
.
Next, you don't need this complex docker ps
expression:
docker ps -a -q -f name="$CONTAINER_DOCKER_NAME"
Docker container names are unique. If you have a container name
stored in $CONTAINER_DOCKER_NAME
, you can just run:
docker exec -it $CONTAINER_DOCKER_NAME ...
This simplifies the docker
command down to:
docker exec -it $CONTAINER_DOCKER_NAME \
/bin/sh -c "FOO=\$BAR $COMMAND"
Note how we're escaping the $
in $BAR
there, because we want that
interpreted inside the container, rather than by our current shell.
Now we just need to arrange to run this via ssh
. There are a couple
of solutions to that. We can just make sure to protect everything on
the command line against the extra level of shell expansion, like
this:
ssh user@$MY_IP "docker exec -it $CONTAINER_DOCKER_NAME \
/bin/sh -c \"FOO=\\\$BAR $COMMAND\""
We need to wrap the entire command in double quotes, which means we
need to escape any quotes inside the command (we can't use single
quotes because we actually want to expand the variable
$CONTAINER_DOCKER_NAME
locally). We're going to lose one level of
\
expansion, so our \$BAR
becomes \\\$BAR
.
If your command isn't interactive, you can make this a little less hairy by piping the script to bash rather than including it on the command line, like this:
ssh user@$MY_IP docker exec -i $CONTAINER_DOCKER_NAME /bin/sh <<EOF
FOO=\$BAR $COMMAND
EOF
That simplifies the quoting and escaping necessary to get things passed through to the container shell.
Upvotes: 3