Direct group of commands into `docker exec`

Viewed 919

I have the following command that works fine and prints foo before returning:

docker exec -i <id> /bin/sh < echo "echo 'foo'"

I want to direct multiple commands into the container with one pipe, for example echo 'foo' and ls /. I have tried the following:

  1. This fails because it runs the commands on the host and pipes the output into the container:
    {
        echo "foo"
        ls /
    } | docker exec -i <id> /bin/sh
    
  2. This fails because it has bad syntax. It also runs on the host:

    {
        echo "foo"
        ls /
    } | docker exec -i <id> /bin/sh
    
  3. This one fails, but I would like to not use an array of strings anyway:

    for COMMAND in 'echo "foo"' 'ls /'
    do
        docker exec -i <id> /bin/sh < echo $COMMAND
    done
    

I've also tried several other methods like piping commands into tee or echo but haven't had any luck. If you would like to know why I want to do this seemingly ridiculous thing, it's because:

  • This is a short script that I would like to keep all in one place
  • I would like to use syntax highlighting, so I don't want to store it all in a list of strings
  • The container has the programs the script should run and the host does not
  • This is an automatic process that I would like to trigger with crontab on the host
3 Answers

You can run a group of commands in the below fashion

 docker exec -i <id> /bin/sh -c 'echo "foo"; ls -l'

OR

docker exec -i 996eee5d121d /bin/sh -c 'echo 'foo'; ls -l'

OR

docker exec -i 996eee5d121d /bin/sh -c 'echo foo; ls -l'

If you want to run more than 2 commands, just append ; after each command like

docker exec -i 996eee5d121d /bin/sh -c 'echo "foo"; ls -l; ls -a'

Use a here document.

docker run -i --rm alpine /bin/sh <<EOF
echo abc
ls /
EOF

Note the difference between quoted and unquoted here document delimiter.

docker exec -i <id> /bin/sh < echo "echo 'foo'"

I think you meant to do:

docker exec -i <id> /bin/sh < <(echo "echo 'foo'")

which is just the same as:

docker exec -i <id> /bin/sh <<<"echo 'foo'"

@edit There is a cool little trick. The idea is to pipe the script itself except first lines to another subprocess, it's sometimes used by installer scripts:

#!/bin/sh
# output this script except first 4 lines to docker
tail -n+5 "$0" | docker run -i --rm alpine /bin/sh -x
exit  # we exit original script
#!/bin/sh
# inside docker now
echo abc
ls /

Execution:

$ bash -x ./script.sh
+ tail -n+5 ./script.sh
+ docker run -i --rm alpine /bin/sh -x
+ echo abc
+ ls /
abc
bin
...
var
+ exit

In a similar fashion you could use sed or another parsing tool to extract the only the relevant part between some marks for example.

I found a gist that explained how to pipe commands into docker exec:

echo "echo foo" | docker exec -i <id> /bin/sh -

Now we need a way to pipe multiple commands. Command groups won't work because they run on the host and semicolon separated commands can get messy. I thought of writing a function and getting just its body, it turns out you can do that with a simple declare and sed call.

You can combine all these pieces to pipe a command into the container:

function func {
    echo "foo"
    ls /
}

declare -f func | sed '1,2d;$d' | docker exec -i <id> /bin/bash -

Syntax highlighting still works in the function and it is easy to read.

If you want to use environment variables that are on the host in the container you have to list them manually in docker exec like so:

... | docker exec -i -e VAR=$VAR <id> /bin/bash -

Edit: I'm leaving this here as a possible solution, but the accepted answer is the proper solution I am using.

Related