2

Like this picture of apt-get, how can I print something at the end of the line, just like the speed on the screen. Although I can use escape like \e[C to make it move several columns to the right and print, but the speed varies and the string length varies. Can anyone tell me how to do this? I am currently programming using bash. Thanks.apt-get

Chromium
  • 517
  • 1
  • 6
  • 21
  • Does it help to your question? [How to add a progress bar to a shell script?](http://stackoverflow.com/questions/238073/how-to-add-a-progress-bar-to-a-shell-script) – Ivan Jan 28 '16 at 03:56
  • Well, actually I've thought of that, but it is kind of trouble to do it manually. – Chromium Jan 28 '16 at 05:42

2 Answers2

4

Often you will find the width of the console window in the environment variable COLUMNS, but this is not completely reliable. A more reliable solution is to use the tput utility (part of ncurses, but can be used standalone) with the argument cols:

$ echo $COLUMNS
148
$ tput cols
148

A simple way to split text is to use the Posix-standard printf utility to first print the text to be aligned on the right margin padded to the column width, then output a carriage return (\r) to return the cursor to the beginning of the line, and then print the text to be aligned on the left margin. Finally a newline will move to the next line. Or, in code:

# Call it like this:
# print_both_sides "This goes on the left" "and this goes on the right."
print_both_sides() {
  printf "\r%*s\r%s\n" $(tput cols) "$2" "$1";
}

The printf format uses the standard feature of * replacements for field width and precision format modifiers; the * means that the corresponding number should be taken from an argument. In this case, I use $(tput cols) -- i.e. the width of the console window -- as the first argument for the format code %*s, which has the effect of right-padding the next string argument ("$2") in a field whose width is precisely the width of the console.

I added an additional carriage return to the beginning of the output in case the cursor is currently not at the left margin.

rici
  • 234,347
  • 28
  • 237
  • 341
  • The `*` means that the field width should be read from an argument. Other than that, it's a normal `%s` with a field width. Eg., `%80s` would right-pad the string argument in a field of 80 spaces. In this case, I use `$(tput cols)` as the field width argument, which will be the width of the console window. – rici Jan 28 '16 at 07:11
  • That's good. Is there a way to do it using `echo` instead of `printf`? – Chromium Jan 28 '16 at 07:13
  • @Chromium: why would you care? `printf` is posix standard and a bash built-in. Posix recommends that you use `printf` instead of `echo`. – rici Jan 28 '16 at 07:15
  • Oh, really. BUT I still care about that because almost every `stdout` in my program uses `echo`. – Chromium Jan 28 '16 at 07:59
  • The lead sentence is incorrect: `COLUMNS` *may* contain value, but that is more of an exception than a general rule, and usually regarded as a nuisance. – Thomas Dickey Jan 28 '16 at 09:01
  • @ThomasDickey: It should have the correct value if you're using `bash`, but I agree that it is not reliable which is why I didn't use it in the later examples. I changed the lede to note the unreliability of COLUMNS, although it might not be a strongly-worded as you would have written it. – rici Jan 28 '16 at 17:08
1

In a terminal, there is more than one way to obtain the actual screen-width:

  • if the terminal/connection can successfully negotiate the screen size, that (usually) results in a correct value shown in stty -a (the columns value usually on the first line of the report).
  • the command-line tput utility consults this information, overriding any constant value for the number of columns from the terminal database, and can print it, e.g, tput cols.
  • the environment variables LINES and COLUMNS usually override that in full-screen applications (including those using curses: see use_env).
  • sometimes the information is just wrong (see the xterm FAQ Why is my screensize not set?). For terminals supporting the VT100-style cursor-position report (also in ECMA-48, 8.3.14 CPR - ACTIVE POSITION REPORT), you can use resize to update the stty setting and optionally update LINES and COLUMNS.

When printing using escape sequences, there are a few different ways to use this information about the screen-width, which are widely supported. The command-line tput utility also can retrieve these from the terminal description (which may lack specific features):

  • absolute cursor positioning, allows you to move the cursor to a specific row and column on the screen: tput cup $row $column
  • horizontal cursor positioning, allows you to move the cursor to a specific column on the current row: tput hpa $column
  • relative cursor positioning, allows you to move the cursor left or right (or up or down), a given number of cells, e.g., tput cuf $value to move forward (to the right) by the given number of cells. Regarding the question, using a parameter like this is usually faster than repeating single movements as done with tputcuf1.

The capabilities cup, hpa, cuf correspond generally to names in ECMA-48 and are described in the terminfo(5) manual page.

To print text at the right-side of the screen, you would do this:

  • obtain the number of columns in the screen,
  • determine the number of columns which your text will use,
  • compute the starting column: right-margin minus the number of columns in the text
  • move the cursor to the starting column
  • print the text, e.g., using echo or printf. The latter has more flexibility.

If you use the command-line printf utility, you may keep in mind that handling multibyte characters such as UTF-8 is not well-supported when you want to know the width of a given string, so portability may be a concern.

Further reading:

Community
  • 1
  • 1
Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105