简体   繁体   中英

Update current line with command line tool in Swift

I built a OS X command line tool in Swift (same problem in Objective-C) for downloading certain files. I am trying to update the command line with download progress. Unfortunately, I cannot prevent the print statement to jump to the next line.

According to my research, the carriage return \\r should jump to the beginning of the same line (while \\n would insert a new line).

All tests have been performed in the OS X Terminal app, not the Xcode console.

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

Still, the display inserts a new line. How to prevent this?

Note: This won't work in Xcode's debugger window because it's not a real terminal emulator and doesn't fully support escape sequences. So, to test things, you have to compile it and then manually run it in a Terminal window.

\\r should work to move to the beginning of the current line in most terminals, but you should also take a look at VT100 Terminal Control Escape Sequences . They work by sending an escape character, \\u{1B} in Swift, and then a command. (Warning: they make some pretty ugly string definitions)

One that you'll probably need is \\u{1B}[K which clears the line from the current cursor position to the end. If you don't do this and your progress output varies in length at all, you'll get artifacts left over from previous print statements.

Some other useful ones are:

  • Move to any (x, y) position: \\u{1B}[\\(y);\\(x)H Note: x and y are Int s inserted with string interpolation.
  • 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 set text foreground and background colors.

If for some reason you can't get \\r to work, you could work around it by saving the cursor state/position just before you print your 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 pre-defined (x, y) position, before printing it:

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)

Your example will only work on the actual command line, not in the debugger console. And you also need to flush stdout for every iteration, like this:

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

Here's an example assuming some async task is taking place with callbacks that pass a Progress type.

// 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 out to the screen following.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM