ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 bash 删除和替换终端中的最后一行?

我想实现一个进度条,在 bash 中显示经过的秒数。为此,我需要擦除屏幕上显示的最后一行(命令“clear”会擦除整个屏幕,但我只需要擦除进度条的那一行并将其替换为新信息)。

最终结果应如下所示:

$ Elapsed time 5 seconds

然后在 10 秒后,我想将这句话(在屏幕上的相同位置)替换为:

$ Elapsed time 15 seconds

D
Derek Veit

回车本身仅将光标移动到行首。如果每一个新的输出行至少和前一个一样长,那没关系,但如果新行较短,前一行不会被完全覆盖,例如:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

要真正清除新文本的行,您可以在 \r 之后添加 \033[K

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

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


这在我的环境中非常有效。有什么兼容性知识吗?
在 Bash 中,至少可以将其缩短为 \e[K 而不是 \033[K
@The Pixel Developer:感谢您尝试改进它,但您的编辑不正确。 return 必须首先将光标移动到行的开头,然后 kill 清除从光标位置到结尾的所有内容,将整行留空,这就是意图。你把它们放在每一行的开头,类似于肯的答案,所以它写在一个空行上。
@DerekVeit 我使用的 zshell 可能有不同的行为。我必须检验我的假设。
只是为了记录,也可以在 \033[K 之前执行 \033[G io \r 虽然显然 \r 要简单得多。此外,invisible-island.net/xterm/ctlseqs/ctlseqs.html 提供了比 Wikipedia 更多的详细信息,并且来自 xterm 开发人员。
K
Ken

用 \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

for i in {1..100000}; do echo -en "\r$i"; done 避免 seq 调用:-)
当您使用诸如“for i in $(...)”或“for i in {1..N}”之类的东西时,您会在迭代之前生成所有元素,这对于大型输入来说效率非常低。您可以利用管道:“seq 1 100000 | while read i; do ...”或使用 bash c 风格的 for 循环:“for ((i=0;;i++)); do ...”
感谢 Douglas 和 tokland - 虽然序列制作不是问题的直接部分,但我已更改为 tokland 更高效的管道
我的矩阵打印机完全弄乱了我的纸张。它不断在不再存在的同一张纸上卡点,这个程序运行多长时间?
如何对 php 和 stdout 做同样的事情?
U
Um.

只要线路长度不超过终端宽度,Derek Veit 的答案就可以很好地工作。如果不是这种情况,以下代码将防止垃圾输出:

在第一次写这行之前,做

tput sc

保存当前光标位置。现在每当你想打印你的行时,使用

tput rc
tput ed
echo "your stuff here"

先回到保存的光标位置,然后从光标到底部清屏,最后写入输出。


奇怪,这在终结者中没有任何作用。你知道是否有兼容性限制吗?
Cygwin 的注意事项:您需要安装包“ncurses”才能使用“tput”。
嗯...如果输出中有超过一行,似乎不起作用。这不起作用:tput sc # save cursor echo '' > sessions.log.json while [ 1 ]; do curl 'http://localhost/jolokia/read/*:type=Manager,*/activeSessions,maxActiveSessions' >> sessions.log.json echo '' >> sessions.log.json cat sessions.log.json | jq '.' tput rc;tput el # rc = restore cursor, el = erase to end of line sleep 1 done
@Nux 您需要使用 tput ed 而不是 tput el。 @Um 的示例使用了 ed (也许他在您发表评论后修复了它)。
仅供参考,这是 tput 命令列表 Colours and Cursor Movement With tput
l
lee8oi

\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"

这确实清除了整条线,问题对我来说闪烁太多了:'(
M
Mr. Goferito

如果进度输出是多行的,或者脚本已经打印了换行符,您可以使用以下内容跳转行:

printf "\033[5A"

这将使光标向上跳 5 行。然后你可以覆盖你需要的任何东西。

如果这不起作用,您可以尝试 printf "\e[5A"echo -e "\033[5A",它们应该具有相同的效果。

基本上,使用 escape sequences,您几乎可以控制屏幕上的所有内容。


可移植的等效项是 tput cuu 5,其中 5 是行数(cuu 向上移动,cud 向下移动)。
@Maëlan 谢谢!您是否知道在运行 tput cuu 5 后如何清除(“重置”)行?
M
Mikael S

使用回车符:

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

这个答案是最简单的方法。您甚至可以使用以下方法实现相同的效果: printf "Foo\rBar"
A
Akif

可以通过放置回车\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

D
Darkman

如果您只想清除前一行,以下可能会解决问题。

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

对于多行,在循环中使用它:

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

这将清除最后 10 行。


M
Matthias Günter

您可以使用 tput,而不是在 echo 中使用反斜杠。为了简化这一点,您可以创建一个函数:

#!/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'