Как перезаписать существующую строку терминальными последовательностями

Как перезаписать существующую строку терминальными последовательностями

Итак, когда wgetвы получаете веб-страницу, она показывает вам строку состояния, которая показывает, насколько загружен файл(ы). Выглядит это примерно так:

25%[=============>______________________________________] 25,000 100.0K/s (подчеркивания — это пробелы; я просто не могу понять, как поставить здесь больше одного пробела подряд)

Однако вместо того, чтобы записать еще одну строку в stdout и добавить еще один индикатор выполнения, он обновляет его, вот так:

50%[===========================>________________________] 50,000 100.0K/s

И wgetэто не единственный пример. Например, когда вы что-то передаёте по конвейеру, lessа затем выходите, ваш исходный запрос остаётся там, вместе с результатом любых команд, которые вы запускали ранее. Как будто вы никуда и не уходили.

Итак, у меня такие вопросы: как это называется, как это реализовать, работает ли это только для одной строки за раз и можно ли использовать это в C?

решение1

Прежде всего, ваш вопрос не имеет ничего общего с bash, а с терминалом. Терминал отвечает за отображение текста программ, а сам bash не контролирует программы после их запуска.

Терминалы предлагают последовательности управления для управления цветом, шрифтом, положением курсора и т. д. Список стандартизированных последовательностей терминалов см. на http://www.termsys.demon.co.uk/vtansi.htmВы можете, например,

  • установите курсор в начале строки
  • удалите строку после этого
  • написать новую строку

для создания индикатора выполнения.

Более сложные управляющие последовательности терминала обычно зависят от терминала, например, работают только с Eterm или xterm.ncurses- это программная библиотека, которая позволяет создавать интерактивные программы с терминалом, чтобы вам не пришлось использовать escape-последовательности.

Как перезаписать существующую строку терминальными последовательностями

echo long text
sleep 1
printf "\033[1A"  # move cursor one line up
printf "\033[K"   # delete till end of line
echo foo

Как перезаписать существующую строку без терминальной последовательности

Одним из простых решений является не писать новую строку в конце, а написать возврат каретки, который по сути сбрасывает курсор в начало строки, например:

echo -n first 
sleep 1 
echo -ne "\rsecond"
echo

Клавиша \rвозврата каретки или курсора переместится в начало строки и позволит вам перезаписать содержимое строки.

Переключайтесь между буферами типа lessилиvi

Поведение lessтакже обусловлено более продвинутой функцией терминала — альтернативным экраном:

В режиме VT102 имеются escape-последовательности для активации и деактивации альтернативного экранного буфера, который имеет тот же размер, что и область отображения окна. При активации текущий экран сохраняется и заменяется альтернативным экраном. Сохранение строк, прокрученных за пределы верхней части окна, отключено до восстановления обычного экрана. Запись term-cap(5) для xterm позволяет визуальному редактору vi(1) переключаться на альтернативный экран для редактирования и восстанавливать экран при выходе. Всплывающее меню упрощает переключение между обычным и альтернативным экранами для вырезания и вставки.

http://rosettacode.org/wiki/Terminal_control/Preserve_screenперечисляет некоторые примеры того, как сделать это самостоятельно, либо черезtputили с помощью некоторых последовательности действий.

решение2

Вместо использования echo, который автоматически добавляет новую строку к строке, используйте printf "%s\r" whatever-- возврат каретки, который отправляет курсор в начало текущей строки. Пример:

seq 1 15 | while read num; do printf "%2d\r" $num; sleep 1; done; echo ""

решение3

Я бы также рекомендовал тем, кто нашел эту тему, взглянуть наBash Prompt HOWTO - Перемещение курсора.

Примеры:

- Position the Cursor:
  \033[<L>;<C>H
     Or
  \033[<L>;<C>f
  puts the cursor at line L and column C.
- Move the cursor up N lines:
  \033[<N>A
- Move the cursor down N lines:
  \033[<N>B
- Move the cursor forward N columns:
  \033[<N>C
- Move the cursor backward N columns:
  \033[<N>D

- Clear the screen, move to (0,0):
  \033[2J
- Erase to end of line:
  \033[K

- Save cursor position:
  \033[s
- Restore cursor position:
  \033[u

Несколько примеров на языке C:

void saveCursorPosition() {
  printf("\033[s");
}

void restoreCursorPosition() {
  printf("\033[u");
}

void lineUP(short int times) {
  printf("\033[%iA", times);
}

void lineDown(short int times) {
  printf("\033[%iB", times);
}

Пример программы:

#include <stdio.h>
#include <unistd.h>

void saveCursorPosition() {
  printf("\033[s");
}

void restoreCursorPosition() {
  printf("\033[u");
}

void lineUP(short int times) {
  printf("\033[%iA", times);
}

void moveCursorBackwards(short int times) {
  printf("\033[%iD", times);
}

void printMainText() {

  printf("\n ╔═══════════════════════════════╗");
  printf("\n ║                               ║");
  printf("\n ║ Progress Bar                  ║");
  printf("\n ║                               ║");
  printf("\n ║ []                            ║");
  printf("\n ║                               ║");
  printf("\n ║ Press Ctrl+C to close         ║");
  printf("\n ║                               ║");
  printf("\n ╚═══════════════════════════════╝\n");

}

int main(int argc, char **argv) {

  printMainText();

  for (int progress=0; progress <= 10; progress++) {

    saveCursorPosition();
    lineUP(5);

    printf("\r ║     [");
    fflush(stdout);

    for (int i=0; i<progress; i++) {
      printf("=>]");
      fflush(stdout);
      moveCursorBackwards(2);
    }

    moveCursorBackwards(progress + 5);

    printf("%i%%", progress * 10);
    fflush(stdout);

    restoreCursorPosition();

    sleep(1);

  }

  return 0;

}

Связанный контент