47

How would I overwrite the previously printed line in a Unix shell with Ruby?

Say I'd like to output the current time on a shell every second, but instead of stacking down each time string, I'd like to overwrite the previously displayed time.

Steph Thirion
  • 9,313
  • 9
  • 50
  • 58

5 Answers5

83

You can use the \r escape sequence at the end of the line (the next line will overwrite this line). Following your example:

require 'time'

loop do
  time = Time.now.to_s + "\r"
  print time
  $stdout.flush
  sleep 1
end
Steph Thirion
  • 9,313
  • 9
  • 50
  • 58
cam
  • 14,192
  • 1
  • 44
  • 29
  • 1
    Hmm, i like @jsegal's solution better, so choose his answer. Remember to `flush` your output still. – cam Feb 10 '11 at 23:15
  • 1
    I'm divided here, because your answer is a perfectly working snippet of code of what I asked exactly: a solution in *Ruby*. Yes, it could be more elegant if it used jsegal's suggestion. But a) jsegal's answer is not ruby specific and b) I'd much rather accept an answer with a working snippet. So I'm going to go ahead and edit your answer to reflect jsegal's suggestion, and when/if that edit is accepted, I'll accept your answer and reward jsegal by voting him up. It's the best I can do under this system. – Steph Thirion Feb 11 '11 at 06:59
  • @Steph: Your dilemma on which answer to accept seems primarily based on the fact that jsegal's answer didn't include a code snippet. Considering Stack Overflow is not a code sharing site, that seems like a fairly arbitrary requirement. You should upvote both answers if you found them useful, and accept the one that helped you the *most* in finding the solution that worked for you. – Cody Gray - on strike Feb 11 '11 at 07:27
  • 1
    Thanks for looking into this @Cody. My dilemma is a little more than that, I think I made it pretty clear that this answer is what I asked for (a solution in *Ruby*). It also meets the S.O. official guidelines: it is the answer that helped me the most. I was stuck until I tried this snippet (turns out I was not flushing). But I believe Stack Overflow is not just about helping one person on a specific moment, it's about building together a reference for the future. Hence my edit. With this in perspective I'll try to submit the edit again. – Steph Thirion Feb 11 '11 at 20:15
  • I have to emphasize the importance of `\r` at the end of `print` (as @jsegal mentioned) otherwise it won't work. – valk Dec 24 '17 at 10:36
  • 3
    This solution works, but I find putting `\r` at the beginning of each line works better, otherwise the cursor covers the first character of the output (at least on my terminal). Also, make sure you're using `print` rather than `puts`, as `puts` automatically ends each line with a hard (`\r\n`) carriage return – Pezholio Dec 11 '18 at 13:57
37

Use the escape sequence \r at the end of the line - it is a carriage return without a line feed.

On most unix terminals this will do what you want: the next line will overwrite the previous line.

You may want to pad the end of your lines with spaces if they are shorter than the previous lines.

Note that this is not Ruby-specific. This trick works in any language!

user664833
  • 18,397
  • 19
  • 91
  • 140
jsegal
  • 1,281
  • 7
  • 6
  • Note that if the text output is wider than the current terminal window, you will start seeing line after line of output again. – Nathan Long Mar 07 '11 at 19:53
  • 5
    To avoid problems with the current line being short than the previous one. Pad it with the method rjust/ljust e.g. "my output".ljust(80). – sunsations Mar 03 '14 at 11:33
2

Following this answer for bash, I've made this method:

def overwrite(text, lines_back = 1)
  erase_lines = "\033[1A\033[0K" * lines_back
  system("echo \"\r#{erase_lines}#{text}\"")
end

which can be used like this:

def print_some_stuff
  print("Only\n")
  sleep(1)
  overwrite("one")
  sleep(1)
  overwrite("line")
  sleep(1)
  overwrite("multiple\nlines")
  sleep(1)
  overwrite("this\ntime", 2)
end
Jacquen
  • 986
  • 8
  • 18
1

Here is an example I just wrote up that takes an Array and outputs whitespace if needed. You can uncomment the speed variable to control the speed at runtime. Also remove the other sleep 0.2 The last part in the array must be blank to output the entire array, still working on fixing it.

#@speed = ARGV[0]

strArray = [ "First String there are also things here to backspace", "Second Stringhereare other things too ahdafadsf", "Third String", "Forth String", "Fifth String", "Sixth String", " " ]


#array = [ "/", "-", "|", "|", "-", "\\", " "]

def makeNewLine(array)
    diff = nil
    print array[0], "\r"
    for i in (1..array.count - 1)
        #sleep @speed.to_f
        sleep 0.2
        if array[i].length < array[i - 1].length
             diff = array[i - 1].length - array[i].length
        end
        print array[i]
        diff.times { print " " } if !diff.nil?
        print "\r"
        $stdout.flush

    end
end

20.times { makeNewLine(strArray) }

#20.times { makeNewLine(array)}
Saloaty
  • 28
  • 5
0

You can use

  1. Ruby module curses, which was part of the Ruby standard library

  2. Cursor Movement

puts "Old line"
puts "\e[1A\e[Knew line"

See also Overwrite last line on terminal:

Fangxing
  • 5,716
  • 2
  • 49
  • 53