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

Я хочу реализовать индикатор выполнения, показывающий прошедшие секунды в bash. Для этого мне нужно стереть последнюю строку, показанную на экране (команда "очистить" стирает весь экран, но мне нужно стереть только строку индикатора выполнения и заменить ее новой информацией).

Конечный результат должен выглядеть так:

$ Elapsed time 5 seconds

Затем через 10 секунд я хочу заменить это предложение (в той же позиции на экране) на:

$ Elapsed time 15 seconds

10 ответов

Решение

Повторить возврат каретки с \r

seq 1 1000000 | while read i; do echo -en "\r$i"; done

от мужского эха:

-n     do not output the trailing newline
-e     enable interpretation of backslash escapes

\r     carriage return

Возврат каретки сам по себе только перемещает курсор в начало строки. Это нормально, если каждая новая строка вывода, по крайней мере, такая же, как и предыдущая, но если новая строка короче, предыдущая строка не будет полностью перезаписана, например:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

Чтобы на самом деле очистить строку для нового текста, вы можете добавить \033[K после \r:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r\033[K0123456789"
0123456789

http://en.wikipedia.org/wiki/ANSI_escape_code

Ответ Дерека Вейта работает хорошо, если длина линии никогда не превышает ширину терминала. Если это не так, следующий код предотвратит вывод нежелательной почты:

прежде чем строка будет написана в первый раз, сделайте

tput sc

который сохраняет текущую позицию курсора. Теперь, когда вы хотите напечатать свою строку, используйте

tput rc
tput ed
echo "your stuff here"

сначала вернитесь в сохраненную позицию курсора, затем очистите экран от курсора до дна и, наконец, запишите результат.

Метод \033 у меня не сработал. Метод \r работает, но на самом деле он ничего не стирает, просто помещает курсор в начало строки. Поэтому, если новая строка короче старой, вы можете увидеть оставшийся текст в конце строки. В конце концов, tput был лучшим способом. У него есть и другие применения, кроме курсора, плюс он предустановлен во многих дистрибутивах Linux и BSD, поэтому он должен быть доступен для большинства пользователей bash.

#/bin/bash
tput sc # save cursor
printf "Something that I made up for this string"
sleep 1
tput rc;tput el # rc = restore cursor, el = erase to end of line
printf "Another message for testing"
sleep 1
tput rc;tput el
printf "Yet another one"
sleep 1
tput rc;tput el

Вот небольшой скрипт обратного отсчета для игры:

#!/bin/bash
timeout () {
    tput sc
    time=$1; while [ $time -ge 0 ]; do
        tput rc; tput el
        printf "$2" $time
        ((time--))
        sleep 1
    done
    tput rc; tput ed;
}

timeout 10 "Self-destructing in %s"

Если результат выполнения многострочный, или сценарий уже напечатал бы символ новой строки, вы можете перейти к следующей строке:

printf "\033[5A"

который заставит курсор прыгать на 5 строк вверх. Тогда вы можете переписать все, что вам нужно.

Если это не сработает, вы можете попробовать printf "\e[5A" или же echo -e "\033[5A", который должен иметь тот же эффект.

По сути, с помощью escape-последовательностей вы можете контролировать практически все на экране.

Можно добиться этого путем размещения возврата каретки \r,

В одной строке кода с printf

for i in {10..1}; do printf "Counting down: $i\r" && sleep 1; done

или с echo -ne

for i in {10..1}; do echo -ne "Counting down: $i\r" && sleep 1; done

Если вы просто хотите очистить предыдущую строку, следующее может помочь.

      printf '\033[1A\033[K'

Для нескольких строк используйте его в цикле:

      for i in {1..10}; do
    printf '\033[1A\033[K'
done

Это очистит последние 10 строк.

Используйте символ возврата каретки:

echo -e "Foo\rBar" # Will print "Bar"

Вместо использования обратной косой черты в вашем echo, вы можете использовать tput. Чтобы упростить это, вы можете создать функцию:

      #!/bin/bash

function consoleoneline {
    # Start process and save all outputs in a temporary directory
    tput sc # save current cursor in console
    local PIPE_DIRECTORY=$(mktemp -d)
    trap "rm -rf '$PIPE_DIRECTORY'" EXIT

    mkfifo "$PIPE_DIRECTORY/stdout"
    mkfifo "$PIPE_DIRECTORY/stderr"

    "$@" >"$PIPE_DIRECTORY/stdout" 2>"$PIPE_DIRECTORY/stderr" &
    local CHILD_PID=$!

    # Replace all outputs with a leading "›"
    # `tput` allows to reset the cursor to the previous line
    sed "s/^/`tput rc;tput el`› /" "$PIPE_DIRECTORY/stdout" &
    sed "s/^/`tput rc;tput el`› /" "$PIPE_DIRECTORY/stderr" >&2 &

    # Wait command has exited, remove temporary directory and reset cursor
    wait "$CHILD_PID"
    rm -rf "$PIPE_DIRECTORY"
    tput ed
}

consoleoneline bash -c 'echo 1 && sleep 1 && echo 2 && sleep 1 && echo 3'

Самый простой способ - это использовать \r характер, я думаю.

Недостатком является то, что вы не можете иметь полные строки, так как он очищает только текущую строку.

Простой пример:

time=5
echo -n "Elapsed $time seconds"
sleep 10
time=15
echo -n "Elapsed $time seconds"

echo "\nDone"
Другие вопросы по тегам