Update current line using command line tool in Swift

I created an OS X command line tool in Swift (same problem in Objective-C) to download specific files. I am trying to update the command line with download progress. Unfortunately, I cannot prevent the print statement from going to the next line.

According to my research, a carriage return \r

should go to the beginning of the same line (while \n

inserting a newline).

All tests were run in the OS X Terminal application, not in the Xcode console.

let logString = String(format: "%2i%% %.2fM \r", percentage, megaBytes)
print(logString)

      

However, the display inserts a new line. How can this be prevented?

+3


source to share


3 answers


Your example will only work on a real command line, not in the debugger console. And you also need to print stdout for each iteration, for example:



var logString = String(format: "%2i%% %.2fM \r", 10, 5)
print(logString)
fflush(__stdoutp)

      

+4


source


Note: this won't work in the Xcode debugger window because it is not a real terminal emulator and does not fully support escape sequences. So, to test things out, you have to compile them and then manually run them in a terminal window.

\r

should work to jump to the start of the current line in most terminals, but you should also take a look at the VT100 terminal switching sequences . They work by sending an escape character \u{1B}

to Swift and then a command. (Warning: they make pretty ugly string definitions)

You will probably need one \u{1B}[K

that clears the line from the current cursor position to the end. If you do not, and your output varies in length, you will receive artifacts from previous print statements.

Some other useful ones:

  • Move to any (x, y) position: \u{1B}[\(y);\(x)H

    Note: x

    and y

    are inserted into Int

    interpolated strings.
  • Save cursor state and position: \u{1B}7

  • Restore cursor state and position: \u{1B}8

  • Clear screen: \u{1B}[2J



You can also do interesting things like setting the color of the text and background.

If for some reason you can't get it to work \r

, you can get around this by saving the state / position of the cursor just before printing logString

and then restoring it after:

let logString = String(format: "\u{1B}7%2i%% %.2fM \u{1B}8", percentage, megaBytes)
print(logString)

      

Or, by moving to a predefined (x, y) position, before printing:

let x = 0
let y = 1 // Rows typically start at 1 not 0, but it depends on the terminal and shell
let logString = String(format: "\u{1B}[\(y);\(x)H%2i%% %.2fM ", percentage, megaBytes)
print(logString)

      

+6


source


Here's an example that assumes doing some asynchronous task with callbacks that pass the type Progress.

// Print an empty string first otherwise whichever line is above the printed out progress will be removed
print("")

let someProgressCallback: ExampleAsyncCallbackType = { progress in
  let percentage = Int(progress.fractionCompleted * 100)

  print("\u{1B}[1A\u{1B}[KDownloaded: \(percentage)%")
}

      

The key part is \u{1B}[1A\u{1B}[K

and then whatever you want to print on the screen.

0


source







All Articles