3

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
GammaGames
  • 1,617
  • 1
  • 17
  • 32
  • 2
    Can you write this into a shell script and `COPY` it into your image, so you don't have to deal with the shell escaping? Better still, make it be the `CMD` of an independent container? – David Maze Jun 03 '20 at 23:45
  • 1
    @DavidMaze I'm not a fan of the first solution because I'd like to be able to tweak this script without needing to rebuild the target container. The second solution might work though, and it might be able to directly connect to the container by its name instead of relying on its id... – GammaGames Jun 04 '20 at 14:08
  • `This one fails, but I would like to not use an array of strings anyway:` Please show what an "array of strings" you would want to use. – KamilCuk Jun 04 '20 at 16:28
  • @KamilCuk The example command is looping over the array of strings, assigning them to the `COMMAND` variable. Array might not be the correct term, but it is still iterating over a list. – GammaGames Jun 04 '20 at 16:44

3 Answers3

4

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'
nischay goyal
  • 3,206
  • 12
  • 23
3

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.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • This is what I was looking for! The syntax highlighting doesn't work in the heredoc which is a shame, but it does look like it's working and looks like a proper solution to my question – GammaGames Jun 04 '20 at 17:00
  • 1
    Sure you can. But lines starting with `#` are comments, so there is no need to remove them. – KamilCuk Jun 05 '20 at 15:00
  • 1
    I deleted my old comments. I can use `sed` to get the content between patterns with the following: `sed -n "/^### SCRIPT START ###/,/^### SCRIPT END ###/p" "$0"` – GammaGames Jun 05 '20 at 15:03
0

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.

GammaGames
  • 1,617
  • 1
  • 17
  • 32