配置shell始终在新行上打印提示符,例如zsh

19

如果命令的输出没有以 \n 结尾,那么下一个提示符会尴尬地立即出现:

$ echo -n hai
hai$

我刚刚注意到一个同事的shell(zsh,如果有用的话)被配置为在这种情况下打印一个%(背景和前景颜色反转以强调),然后是一个\n

$ echo -n hai
hai%
$

我也想这样做。我使用Bash。这可行吗?如果可以,我应该在我的~/.bashrc文件中添加什么内容?


更新

我花了几个小时来理解gniourf_gniourf的解决方案。我将在此分享我的发现,以便对其他人有用。

以下是我.bashrc文件中相关的代码片段:

set_prompt() {
  # CSI 6n reports the cursor position as ESC[n;mR, where n is the row
  # and m is the column. Issue this control sequence and silently read
  # the resulting report until reaching the "R". By setting IFS to ";"
  # in conjunction with read's -a flag, fields are placed in an array.
  local curpos
  echo -en '\033[6n'
  IFS=';' read -s -d R -a curpos
  curpos[0]="${curpos[0]:2}"  # strip leading ESC[
  (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'

  # set PS1...
}

export PROMPT_COMMAND=set_prompt
注意:第curpos[0]="${curpos[0]:2}"行是不必要的。我将其包含在内,以便这段代码可在需要相关行数的上下文中使用。

为什么不问他是如何完成的。他的 ~/.zshrc 中的代码片段很可能与您的 ~/.bashrc 中的相同。这个问题可能更适合 Server Fault,但如果您只想追加到没有 \n 换行符的行上,那么这个问题也可以回答。 - MattSizzle
我确实问过他,我们查看了他的~/.zshrc文件,但是没有找到负责代码。是的,我只想追加输出,而不是以\n结尾的输出。 - davidchambers
1
这是昨晚我在谷歌上找不到的东西,似乎它是zch特定的,因为有很多新行处理信息。今晚我会进一步研究这个问题,因为我现在想知道如何自己做这件事。 - MattSizzle
1
我应该在我的~/.bashrc文件中添加什么?exec zsh - Kevin
zsh在内部使用$COLUMN - 1 spaces then \r trick,如果有人仍然好奇为什么他们找不到它在他们朋友的.zshrc文件中。 - Tomáš Janoušek
3个回答

15

使用 PROMPT_COMMAND 的一个小技巧:

在 Bash 打印每个主提示符之前,会检查变量 PROMPT_COMMAND 的值。如果设置了 PROMPT_COMMAND 并且具有非空值,则该值将被执行,就像在命令行上键入它一样。

因此,如果您将以下内容放入您的 .bashrc 文件中:

_my_prompt_command() {
    local curpos
    echo -en "\E[6n"
    IFS=";" read -sdR -a curpos
    ((curpos[1]!=1)) && echo -e '\E[1m\E[41m\E[33m%\E[0m'
}
PROMPT_COMMAND=_my_prompt_command

你会变得非常好。在echo "%"部分中,可以自由地使用其他花哨的颜色。甚至可以将其内容放在变量中,以便您可以随时修改它。

诀窍:在打印提示之前获取光标的列(使用echo -en "\E[6n"命令,然后是read命令),如果不是1,则打印一个%和一个换行符。

优点:

  • 纯bash(无外部命令),
  • 没有子shell,
  • 使您的PS1保持整洁:如果有时要更改PS1(当我在深层嵌套目录中工作时,我会这样做-我不喜欢提示符跑了几英里),这仍将起作用。

正如tripleee所评论的那样,您可以使用stty代替回显硬编码的控制序列。但这使用了外部命令,不再是纯bash。根据您的需求进行调整。


关于您遇到的随机打印丑陋字符代码的问题:这可能是因为tty缓冲区中仍然有一些内容。可能有几种解决方法:

  1. Turn off and then on the echo of the terminal, using stty.

    set_prompt() {
        local curpos
        stty -echo
        echo -en '\033[6n'
        IFS=';' read -d R -a curpos
        stty echo
        (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
    }
    PROMPT_COMMAND=set_prompt
    

    the main difference is that the echo/read combo has been wrapped with stty -echo/stty echo that respectively disables and enables echoing on terminal (that's why the -s option to read is now useless). In this case you won't get the cursor position correctly and this might lead to strange error messages, or the % not being output at all.

  2. Explicitly clear the tty buffer:

    set_prompt() {
        local curpos
        while read -t 0; do :; done
        echo -en '\033[6n'
        IFS=';' read -s -d R -a curpos
        (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
    }
    PROMPT_COMMAND=set_prompt
    
  3. Just give up if the tty buffer can't be cleaned:

    set_prompt() {
        local curpos
        if ! read -t 0; then
            echo -en '\033[6n'
            IFS=';' read -s -d R -a curpos
            (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
        # else
        #     here there was still stuff in the tty buffer, so I couldn't query the cursor position
        fi
    }
    PROMPT_COMMAND=set_prompt
    

补充一点:不必读取数组curpos,你可以直接获得光标的位置,例如用变量curxcury

IFS='[;' read -d R _ curx cury

如果你只需要y轴位置cury

IFS='[;' read -d R _ _ cury

1
很棒!这是一个漂亮的解决方案。最后一个问题:为什么要引用函数名,而不是简单地使用PROMPT_COMMAND=_my_prompt_command - janos
@janos 感谢您的评论。您是正确的,引号在那里是无用的,我会将它们删除。 - gniourf_gniourf
我想你可以通过使用stty而不是回显硬编码的控制序列来获得一些可移植性。 - tripleee
@tripleee 你说得对。我只想让它是100%纯的bash,不使用外部命令。 - gniourf_gniourf
你不需要使用 stty 内联命令。可以像这样使用 q=$(stty 命令); fun () { echo -n "$ q"; } - tripleee
显示剩余6条评论

4

感谢unix.stackexchange上的Gilles:

如果上一个命令把光标留在了最后一行以外,你可以让bash在下一行显示提示符。将以下内容添加到你的.bashrc文件中(GetFree根据Dennis Williamson的建议进行了修改)

我从这两个链接的答案中提炼出了这个解决方案:

PS1='\[\e[7m%\e[m\]$(printf "%$((COLUMNS-1))s")\r$ '

解释:

  • \[\e[7m%\e[m\] -- 反色百分号符号
  • printf "%$((COLUMNS-1))s" -- COLUMNS-1 个空格。如果设置了 checkwinsize 选项,COLUMNS 变量将存储终端的宽度。由于 printf 在一个 $() 子 shell 中,所以它的输出不会打印到屏幕上,而是添加到 PS1 中。
  • \r 回车符

所以,基本上这是一个 % 符号,后面跟着一长串空格,然后是回车键。这种方法是有效的,但说实话,我不明白为什么会产生期望的效果。具体来说,为什么只有在需要时才会看起来像添加了换行符,否则没有额外的换行符呢?那些空格为什么是必要的呢?


这并没有完全回答你的问题,因为我不知道如何像zsh一样在行末放置。但是Dennis Williamson的答案(你链接的那个)可以解决这个问题。 - davidchambers
1
它的工作原理是根本不输出换行符 - 它会在屏幕右侧留出空间,然后输出回车符 - 而不是换行符 - 以将光标移动到当前行的开头。如果前一行结束位置不在行首,则空格将换行,回车符只会移到那一行的开头。 - evil otto
@evilotto,空格的数量怎么样?为什么要精确地有 COLUMNS-1 个空格? - janos

0

如果你执行echo $PS1,你会看到你的提示符的当前代码,就像这样:
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$

现在在前面加上一个\n,像这样:

PS1="\n\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$"

现在你的提示符将始终从新的一行开始。


@davidchambers 是的 :-) - thom
是的,我一开始也考虑了PS1,但是操作者希望仅在前一个输出没有/n返回时才进行附加。 - MattSizzle
1
你完全没有回答问题。OP只要求在输出没有换行符时添加一个换行符,并使用反向“%”进行指示。顺便说一句,我刚刚安装了zsh,它默认情况下就会执行此操作,所以这可能是魔法... - janos
@janos:不要太苛刻了,我已经向他展示了如何在提示符中添加类似于\n的内容。如果你知道更好的方法,请给我看代码。 - thom
@thom,我并不是要严厉批评你。但我仍然认为你没有完全理解他的意思。我的直觉告诉我这里可能不会有一个好的答案。这可能是zsh的魔法,而bash无法实现。 - janos
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接