Exit code from docker-compose breaking while loop

I've got case: there's WordPress project where I'm supposed to create a script for updating plugins and commit source changes to the separated branch. While doing this I had run into a strange issue.

Input variable:

    akimset,4.0.3
    all-in-one-wp-migration,6.71

What I wanted to do was iterating over each line of this variable

    while read -r line; do
      echo $line
    done <<< "$variable"

and this piece of code worked perfectly fine, but when I have added docker-compose logic everything started to act weirdly

    while read -r line; do
      docker-compose run backend echo $line
    done <<< "$variable"

now only one line was executed and after this script exited with 0 and stopped iterating. I have found workaround with:

    echo $variable > file.tmp
    for line in $(cat file.tmp); do
      docker-compose run backend echo $line
    done

and that works perfectly fine and it iterates each line. Now my question is: why? ZSH and shell scripting could be a bit misterious and running in edge-cases like this one isn't anything new for me, but I'm wondering why succesfully executed script broke input stream.

Upvotes: 4

Views: 1899

Answers (4)

Don Benjamin
Don Benjamin

Reputation: 23

You can use redirection to prevent the first docker-compose command from reading all of stdin. e.g. if you redirect from /dev/null:

seq 3 | while read num; do
    docker-compose run --rm bash echo "Run number $num" </dev/null;
done

Outputs:

Creating tmp_bash_run ... done
Run number 1
Creating tmp_bash_run ... done
Run number 2
Creating tmp_bash_run ... done
Run number 3

But if you don't add the redirection then the first docker-compose command will read all the remaining input from stdin ending the loop after a single run.

seq 3 | while read num; do
    docker-compose run --rm bash echo "Run number $num";
done

Outputs:

Creating tmp_bash_run ... done
Run number 1

This is useful if, like me, you are using a recent version of docker, you need the output of the command you are running and you don't want to edit your docker-compose.yml file.

To run the above commands you'll need a docker-compose.yml file with something like the following:

---
services:
  bash:
    image: bash

Upvotes: 0

wlarcheveque
wlarcheveque

Reputation: 972

I was able to solve the same problem by using a different loop :

for line in $(cat $variable)
do
    docker-compose run backend echo $line
done

Upvotes: 1

Luis Andres Giordano
Luis Andres Giordano

Reputation: 421

The problem with this

while read -r line; do
  docker-compose run backend echo $line
done <<< "$variable"

is that docker allocate pseudo-TTY. After the first execution of docker-compose run (first loop) it access to the terminal using up the next lines as input.

You have to pass -T parameter to 'docker-compose run' command in order to avoid docker allocating pseudo-TTY. Then, a working code is:

while read -r line; do
  docker-compose run -T backend echo $line
done < $(variable)

Update

The above solution is for docker version 18 and docker-compose version 1.17. For newer version the parameter -T is not working but you can try:

  1. -d instead of -T to run container in background mode BUT no you will not see stdout in terminal.
  2. If you have docker-compose v1.25.0, in your docker-compose.yml add the parameter stdin_open: false to the service.

Upvotes: 2

mdip
mdip

Reputation: 685

I ran into a nearly identical problem about a year ago, though the shell was bash (the command/problem was also slightly different, but it applied to your issue). I ended up writing the script in zsh.

I'm not certain what's going on, but it's not actually the exit code (you can confirm by running the following):

variable=$'akimset,4.0.3\nall-in-one-wp-migration,6.71'
while read line; do docker-compose run backend print "$line"; print "$?"; done <<<($variable)

... which yielded ...

(akimset,4.0.3
0

(I'm not at all sure where the ( came from and perhaps solving that would answer why this problem happens)

Working Script

for line in "${(f)variable}"; do
    docker-compose run backend echo "$line"
done

The (f) flag tells zsh to split on newlines; the "${(f)variable" is in quotes so that any blank lines aren't lost. If you're going to include escap sequences that you want to not be converted to the corresponding values (something that I often need when reading file contents from a variable), make the flags (fV)

Upvotes: 0

Related Questions