Homepage: www.whit537.org           Email: chad@zetaweb.com

Monday, November 24, 2008

A use for the carriage return

I just found out what the carriage return character, \r, is good for. A friend was asking for help on a command-line program: he wanted to update a progress indicator. This is like you get when downloading something with curl or wget or fetch. So I opened up /usr/src/usr.bin/fetch/fetch.c, expecting to find some low-level TTY hackery. Instead, I found this key line:

fprintf(stderr, "\r%-46.46s", xs->name);

It turns out that the carriage return moves the output cursor back to the beginning of the current line, so further output overwrites whatever was already there. It turns out, too, that this works from Python on both Unix and Windows:

>>> print "foo\rbar"
bar

Yay!

This makes sense if you think about where the term "carriage return" comes from, and it also makes sense out of why Windows includes it as part of its EOL indicator. I have a typewriter at home, and I've been using it lately to type invoices for the little bit of consulting that I still do. The bar on my typewriter that ends a line actually does perform two functions: first it scrolls the paper up—newline, \n. Then if you keep pushing the bar, it moves the carriage (the roller assembly that holds the paper) back to the right until you hit your first tab stop—carriage return, \r. So on my typewriter the EOL sequence is \n\r, but apparently on teletypes the order was reversed. And I actually do perform a \n without a \r sometimes, like when I'm lining up columns on an invoice. I can do a \r without a \n too, but that's a different knob.

Unlike so many Unix conventions that are the result of ossified traditions from more innocent times, it appears that the single-character EOL is actually an innovation. It's fun to know your roots.

14 comments:

Mr. New Hacker said...

You and rapid research; Don't you have a job? ;^) Again, thanks for the help.

Dougal said...

Thats awesome. I always wondered how that worked...

Gabriel said...

I tried to use that some time ago, but did not manage to print without '\n' so this does'nt work

#!/usr/bin/env python
import time

for i in range(100):
time.sleep(.01)
print '\r'+i*'='+'>'

any idea?

Gabriel said...

#!/usr/bin/env python
import time

for i in range(100):
____time.sleep(.01)
____print '\r'+i*'='+'>'

indentation with '_' sorry.

Anonymous said...

@Gabriel
print is putting a '\n' at the end.

you can either use

print '\r'+str(i),
# the comma prevent the newline

or
sys.stdout.write('\r'+str(i))

in either case you need to call
sys.stdout.flush() to flush the lines when there isn't a newline at the end.

Gabriel said...

@Anonymous: Thanks a lot :)
sooooooooo nice this way :D

whit537 said...

Gabriel: what she said.

However I'm not seeing the comma suppress the newline:

>>> print 'foo',
foo
>>> import sys
>>> sys.stdout.write('foo')
foo>>>

That'd be a neat trick if it worked.

David Niergarth said...

Here's an animated example showing write and flush.

import sys
import time

sleep = time.sleep
write = sys.stdout.write
flush = sys.stdout.flush

message = 'PYTHON'
for i, c in enumerate(message):
    write('_' * i + c)
    flush()
    sleep(.5)
    write('\r')

write(message)
flush()
sleep(.5)
write('!')
flush()
sleep(.5)

While '\r' returns to the beginning of the line, it doesn't clear what's already on the line so you may need to pad your text with trailing spaces to cover the previous text.

Paddy3118 said...

Every once in a while, along comes a post that reminds me of just how much baggage I still have sloshing around in my head.

Here is a 'spinner'

bash$ python -c '
> import time,sys
> while True:
> ..for char in r"/-\|":
> ....sys.stdout.write("\r" + char)
> ....sys.stdout.flush()
> ....time.sleep(0.1)
> '

(dots for indentation)

Anonymous said...

Try:

print('\rvery long status', end='')
print('\rshort status', end='')

To solve this use:

print('\rvery long status', end='')
print('\r\x1b[K\rshort status', end='')

More at: http://www.termsys.demon.co.uk/vtansi.htm

whit537 said...

Oooh, neat! And extra points for 3.0 syntax, Anonymous. ;^)

Ryan said...

I was trying to figure out how to make a countdown clock, and this totally helped me out. Thanks!

import sys
import time

flush = sys.stdout.flush
sleep = time.sleep

print 'How much time do you want on the clock?',
answer = raw_input()
answer = answer.split(':')

hrs = int(answer[0])
mns = int(answer[1])
sec = int(answer[2])

pause = 1

while sec >= 0:
print '\r%02i:%02i:%02i' % (hrs, mns, sec),
flush()
sleep(pause)
sec -= 1
if sec == 0:
if mns > 0:
mns -= 1
sec += 59
elif mns == 0 and hrs > 0:
hrs -= 1
mns += 59
sec += 59

theY4Kman said...
This post has been removed by the author.
theY4Kman said...

I discovered this when I was developing my first bootloader. Whenever I printed text and ended it with a newline, it would skip to the next line at the same column:

Welcome to the YakOS bootloader!
                                Now loading stage 2....

I think having \r\n as the EOL indicator was an easy way to print a file correctly to stdout.