tec*_*ard 7 bash prompt io-redirection shell-script
我编写了一个精美的自定义 bash 提示符,并且效果非常好;我只是在尝试运行由换行符分隔的多个命令时遇到问题。(对于长度我深表歉意,但我希望我的问题是清楚的)
TL;DR:从 with 读取光标位置stdin会抛出那里的任何数据。数据不能丢弃,请指教。
当我运行一个长时间运行的命令,该命令不从标准输入读取(例如sleep 5)并键入下一个命令+输入(例如ls -lah<enter>)时,一旦完成,bash就会开始从标准输入读取,读取命令+输入并开始运行它(ls -lah在这种情况下)。我将称之为“急切的情况”,而不是在打字之前等待下一个提示出现的“耐心的情况”。(以下示例)
PS1使用类似的默认 bash 提示符(又名) PS1='[\u@\h \W]\$ ',它可以按预期工作。
# wait for new prompt to show up and type new command
[me@machine ~]$ sleep 5; echo 'sleep done!'<enter>
sleep done!
[me@machine ~]$ ls -A /etc/skel<enter>
.bash_logout .bash_profile .bashrc
[me@machine ~]$
Run Code Online (Sandbox Code Playgroud)
# we immediately start typing our next command to be run after we typed the sleep+echo
[me@machine ~]$ sleep 5; echo 'sleep done!'<enter>
ls -A /etc/skel<enter>
sleep done!
# bash now auto-fills the prompt because it reads it from the tty/stdin
# and immediately runs it (because it ends with a newline):
[me@machine ~]$ ls -A /etc/skel
.bash_logout .bash_profile .bashrc
[me@machine ~]$
Run Code Online (Sandbox Code Playgroud)
患者情况良好。这里没有问题。
问题出在precmd()函数的启动处。(我正在使用这个 bash-preexec 钩子)
在那里,我调用了一个函数,该函数向终端询问当前光标坐标,以便知道是否应该打印额外的换行符,以防上次运行命令的输出未以 1 结尾。我从另一篇 SO 帖子中获得了该功能:/sf/answers/3706128471/
如果我禁用此功能,则不会出现该问题。
function precmd() {
# must be 1st
previous_command_exit="${?}"
# saves cursore coordinates to 2 variables: _cursor_col, _cursor_row
# If I comment-out the call to _fetch_cursor_position, I can use the `eager situation` as expected.
_fetch_cursor_position
# add extra newline if command did not end with a newline
[[ "${_cursor_col}" -gt 1 ]] && printf "\n"
# ...
}
Run Code Online (Sandbox Code Playgroud)
就我而言,它的定义如下:
_fetch_cursor_position() {
local -a pos
IFS='[;' read -p $'\e[6n' -d R -a pos -rs || echo >&2 "failed with error: $? ; ${pos[*]}"
_cursor_row="${pos[1]}"
_cursor_col="${pos[2]}"
}
Run Code Online (Sandbox Code Playgroud)
这个函数的工作方式是这样的(请参阅 SO 帖子以获得更好的解释):
^[[6n到终端,这要求终端报告光标位置。这是read由 (ab)using-p prompt标志打印的(参见help read)。^[[y;xR在终端中“键入”来报告光标位置,其中:^[后跟文字[
y:从顶部开始的行号(可以大于 9)Rread命令通过从 stdin 读取来解析该值并将其分配给pos数组:^[(我们不关心这个)主要是防止该_fetch_cursor_position函数读取 tty/stdin 中已输入的数据。由于这可能是不可能的,也许有一种方法可以保存标准输入的当前值,读取光标位置,然后恢复标准输入值。我对 bash 进行了一些研究,coproc因为它看起来很有前途;但我不确定那会如何运作。
我有一种强烈的感觉,解决方案在于使用 bash 的 I/O 重定向做一些“高级”的事情。但我实际上只使用过>/dev/null、&>/dev/null、2>&1和|&文件/子 shell 重定向,例如< <(echo abc)和>myfile.txt,从来没有直接使用额外的 FD。
我脑子里的流程是这样的:
0到新的文件描述符(stdout/1也是吗?)/dev/tty/ $(tty)?),以便它们在完全相同的终端上有空的(当然,以上所有操作都无需移动光标)让终端以某种方式将坐标响应写入0不同的 FD,而不是写入 stdin/,并read从那里读取。
当我使用稍微编辑过的 subshell 扩展版本时_fetch_cursor_position,这也不起作用,因为它继承了 stdin/stout/stderr:
_echo_cursor_position() {
local pos
IFS='[;' read -p $'\e[6n' -d R -a pos -rs || echo >&2 "failed with error: $? ; ${pos[*]}"
_cursor_row="${pos[1]}"
_cursor_col="${pos[2]}"
# this line is added, no other changes apart from the name
printf "${_cursor_row} ${_cursor_col}"
}
function precmd() {
# must be 1st
previous_command_exit="${?}"
#_fetch_cursor_position
pos=( $(_echo_cursor_position) )
_cursor_row="${pos[0]}"
_cursor_col="${pos[1]}"
# add extra newline if command did not end with a newline
[[ "${_cursor_col}" -gt 1 ]] && printf "\n"
# ...
}
Run Code Online (Sandbox Code Playgroud)
如果可以解决这个继承问题,也许这将是一个更简单和/或更优雅的解决方案。
read没有任何重定向光标位置已正确保存,但急切键入的命令消失了。
IFS='[;' read -r -s -p $'\e[6n' -d 'R' __garbage __cursor_col __cursor_row
Run Code Online (Sandbox Code Playgroud)
read开启两个重定向/dev/tty如果我正确理解 bash,stdin/stdout(和 stderr)默认连接到 tty,所以这不应该与 1 不同,但事实并非如此。
IFS='[;' read -r -s -p $'\e[6n' -d 'R' __garbage __cursor_col __cursor_row </dev/tty >/dev/tty
Run Code Online (Sandbox Code Playgroud)
从man 1 bash:
交互式 shell 是一种不带非选项参数(除非指定 -s)且不带 -c 选项的 shell,其标准输入和错误都连接到终端(由 isatty(3) 确定),或者是一种以 - 启动的 shell。我的选择。如果 bash 是交互式的,则设置 PS1 并包含 $- ,从而允许 shell 脚本或启动文件测试此状态。
printf和read与 1 和 2 相同的结果
printf $'\e[6n' >/dev/tty
IFS='[;' read -r -s -d 'R' __garbage __cursor_col __cursor_row </dev/tty
Run Code Online (Sandbox Code Playgroud)
$(tty)代替/dev/tty再说一遍,没有区别
local tty="$(tty)"
printf $'\e[6n' >"${tty}"
IFS='[;' read -r -s -d 'R' __garbage __cursor_col __cursor_row <"${tty}"
Run Code Online (Sandbox Code Playgroud)
不要做所有这些愚蠢的努力,只是总是打印一个额外的换行符。
从完美主义者的角度来看,我真的不想这样做。
我总是使用 tmux,所以这对我来说是一个很好的实用解决方案,只在必要时打印额外的换行符。
如果有不需要 tmux 的 bash-native/terminal-native 解决方案,我很想听听。
这就是问题:
\n\n\n终端通过“键入”\xe2\x80\xa6 报告光标位置
\n
对于从终端读取的任何程序来说,您键入的内容和终端“键入”的内容没有区别。
\n使用为此类事情提供单独通道的终端。我使用tmux(出于其他原因)。在tmux以下作品中:
_fetch_cursor_position() {\n local pos\n pos="$(tmux display-message -p -F \'#{cursor_x} #{cursor_y}\')"\n _cursor_row="${pos#* }"\n _cursor_col="${pos% *}"\n ((_cursor_row++))\n ((_cursor_col++))\n}\nRun Code Online (Sandbox Code Playgroud)\n关键是这个函数根本不从标准输入读取。
\ntmux计算从 开始的行/列0。我的代码中的这些++只是为了使您获得的数字与当前代码的其余部分兼容(显然期望从 开始计数1)。如果我从头开始编写,我不会使用++; 我会相应地编写测试(例如,-gt 0而不是-gt 1在precmd())。