-2

Would appreciate a suggestion for a workaround or fix:

Python 3.9 running in docker-compose 3.8 and using CMD [ "python3", "-u", "app.py" ]

print('text', end='\r', flush=True) will not print to terminal - want this printed.

print('text', flush=True) will print to terminal.

Yes, using both "python3", "-u" and flush=True is an overkill. ‍♂️

aaron
  • 39,695
  • 6
  • 46
  • 102
MiKK
  • 89
  • 7
  • This might be helpful. https://stackoverflow.com/questions/493386/how-to-print-without-a-newline-or-space It seems an empty string when specifying end is sufficient. – nikost Jan 20 '22 at 06:21
  • 4
    What do you expect the `end='\r'` to do? – Klaus D. Jan 20 '22 at 06:22
  • 5
    Using `end='\r'` you're setting cursor to start of the same line instead of moving to new line and next `stdout` value will override the previous. – Artyom Vancyan Jan 20 '22 at 08:29
  • 1
    Short answer: `docker-compose run app` instead of `docker-compose up`. The explanation is in my answer below. – aaron Jan 29 '22 at 11:07

3 Answers3

4

The print() function will print all characters in your message, then append the end character after printing. If you omit end= it defaults to a newline character (\n on nix). The \r character moves the cursor to the beginning of the same line.

So lets look at a few situations in the python interpreter:

# print 'text' followed by \r (move to beginning of line) followed by newline
>>> print('text\r')
asdf
>>>

In this example, text is printed, the cursor then moves to the beginning of the same line, then the default newline character is printed moving to the next line. Finally, python prints its prompt.

# print 'text' followed by \r followed by nothing
>>> print('text', end='\r')
>>>

In this example, text is printed, the cursor moves to the beginning of the same line, then the command is finished and the interpreter prints its prompt >>> overwriting the text that was printed.

# print 'text followed by \r followed by nothing (alternate)
>>> print ('text\r', end='')
>>>

The above is identical to the previous example.

The situation is very similar when you run your script using python from the terminal, except instead of the python interpreter printing its prompt, your shell is printing its prompt after the program executes.

It's difficult to suggest a "fix" here because it's unclear what you actually want to accomplish.

drootang
  • 2,351
  • 1
  • 20
  • 30
  • Thank you for these comparisons. The purpose is to run python3 in docker-compose, where python3 prints statements in terminal; specifically, when a statement is printed, the carriage is returned to the beginning of the line and, when the next statement is printed, it overwrites the previous output in the terminal. I suspect by observation that docker-compose has a bug that does not allow output of statements that include '\r'. Most likely I'll need to raise this as issue in docker's git repo. – MiKK Jan 28 '22 at 20:49
  • @MatveiKruglyak Edit your question to mention that. – aaron Jan 29 '22 at 11:16
2

The output of docker-compose up is aggregated 1 and effectively line-buffered 2 (see Sample log), meaning it only prints when a \n is seen. There are other anecdotes 3,4 of this behaviour with \r.

You can see that it is actually printed by running docker-compose run app 5.

References:

  1. https://docs.docker.com/compose/reference/up/
  2. https://eklitzke.org/stdout-buffering
  3. https://github.com/docker/compose/issues/1549#issuecomment-745086053
  4. Output of pv on docker-compose startup not working as expected
  5. https://docs.docker.com/compose/faq/#whats-the-difference-between-up-run-and-start

Workarounds

1. Run a single app directly

Run docker-compose run app, as mentioned above.

The output of docker-compose run goes directly to the stdout of your terminal.

2. Duplicate the output to a volume-mounted file and watch it

tee the output in CMD to a file in a mounted volume and then tail -F that file.

touch app.log
docker-compose up & tail -F app.log

(Ctrl+C to exit from tail -F app.log.)

Sample files

app.py:

from time import sleep
  
for i in range(10):
    print('text: {}'.format(i), end='\r')
    sleep(0.3)

Dockerfile:

FROM python:3.7-alpine
WORKDIR /app

COPY . .
# CMD [ "python3", "-u", "app.py" ]
CMD python3 -u app.py > app.log

docker-compose.yml:

version: "3.9"
services:
  app:
    build: .
    volumes:
      - ./:/app

Sample log

From docker-compose up without workaround:

==> /var/lib/docker/containers/xxx/xxx-json.log <==
{"log":"text: 0\rtext: 1\rtext: 2\rtext: 3\rtext: 4\rtext: 5\rtext: 6\rtext: 7\rtext: 8\rtext: 9\r","stream":"stdout","time":"xxx"}

Thus, print('text: 0\rtext: 1\rtext: 2\rtext: 3\rtext: 4\rtext: 5\rtext: 6\rtext: 7\rtext: 8\rtext: 9\r') effectively prints text: 9\r.

How to view logs in macOS

  1. Open a shell in the VM using nc -U ~/Library/Containers/com.docker.docker/Data/debug-shell.sock.
    Ref: https://docs.docker.com/desktop/mac/release-notes/2.x/#docker-desktop-community-2400
  2. Run tail /var/lib/docker/containers/*/*.log.
aaron
  • 39,695
  • 6
  • 46
  • 102
0

I think that you just need to prepend \r, for example:

import time

for i in range(10):
    print('\rcount {}'.format(i), end='')
    time.sleep(1)

Here you will see the message count n, changing every second.

Jonathan Quispe
  • 586
  • 3
  • 10