考虑一下这个 PS1
PS1='\n${_:+$? }$ '
这是几个命令的结果
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
1 $
第一行显示没有预期的状态,接下来的两行显示正确的退出代码。但是在第 3 行,只有 Enter 被按下,所以我希望状态消失,就像第 1 行一样。我该怎么做?
考虑一下这个 PS1
PS1='\n${_:+$? }$ '
这是几个命令的结果
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
1 $
第一行显示没有预期的状态,接下来的两行显示正确的退出代码。但是在第 3 行,只有 Enter 被按下,所以我希望状态消失,就像第 1 行一样。我该怎么做?
这是一个有趣的、非常简单的可能性:它使用\#
转义序列PS1
和参数扩展(以及 Bash 扩展其提示符的方式)。
转义序列\#
扩展为要执行的命令的命令号。每次实际执行命令时都会递增。试试看:
$ PS1='\# $ '
2 $ echo hello
hello
3 $ # this is a comment
3 $
3 $ echo hello
hello
4 $
现在,每次显示提示时,Bash 首先扩展在 中找到的转义序列PS1
,然后(假设设置了 shell 选项promptvars
,这是默认值),通过参数扩展、命令替换、算术扩展和报价删除。
然后,诀窍是有一个数组,只要执行第 ( k -1) 个命令,就会将第k个字段设置为(空字符串) 。然后,使用适当的参数扩展,我们将能够检测这些字段何时设置,并在未设置该字段时显示上一个命令的返回码。如果要调用此数组,只需执行以下操作:__cmdnbary
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
看:
$ PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
0 $ [ 2 = 3 ]
1 $
$ # it seems that it works
$ echo "it works"
it works
0 $
要获得最短答案挑战的资格:
PS1='\n${a[\#]-$? }${a[\#]=}$ '
那是31个字符。
当然,不要使用这个,因为这a
是一个太琐碎的名字;另外,\$
可能比$
.
似乎您不喜欢最初的提示是0 $
; 您可以通过适当地初始化数组来非常轻松地修改它__cmdnbary
:您将把它放在配置文件中的某个位置:
__cmdnbary=( '' '' ) # Initialize the field 1!
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ '
这个周末有时间玩。查看我之前的答案(不好)和其他答案,我认为这可能是最小的答案。
将这些行放在您的末尾~/.bash_profile
:
PS1='$_ret$ '
trapDbg() {
local c="$BASH_COMMAND"
[[ "$c" != "pc" ]] && export _cmd="$c"
}
pc() {
local r=$?
trap "" DEBUG
[[ -n "$_cmd" ]] && _ret="$r " || _ret=""
export _ret
export _cmd=
trap 'trapDbg' DEBUG
}
export PROMPT_COMMAND=pc
trap 'trapDbg' DEBUG
然后打开一个新终端并在 BASH 提示符下记下这种期望的行为:
$ uname
Darwin
0 $
$
$
$ date
Sun Dec 14 05:59:03 EST 2014
0 $
$
$ [ 1 = 2 ]
1 $
$
$ ls 123
ls: cannot access 123: No such file or directory
2 $
$
解释:
trap 'handler' DEBUG
和PROMPT_COMMAND
钩子的。PS1
正在使用变量,_ret
即PS1='$_ret$ '
。trap
command 仅在执行命令时运行,但PROMPT_COMMAND
即使按下空回车也会运行。trap
command 使用 BASH internal var 将变量_cmd
设置为实际执行的命令BASH_COMMAND
。PROMPT_COMMAND
hook 设置_ret
为"$? "
if_cmd
为非空,否则设置_ret
为""
. 最后它将 var 重置_cmd
为空状态。HISTCMD
每次执行新命令时都会更新该变量。不幸的是,该值在执行过程中被屏蔽PROMPT_COMMAND
(我想是因为历史没有与提示命令中发生的事情混淆)。我想出的解决方法有点混乱,但它似乎在我有限的测试中有效。
# This only works if the prompt has a prefix
# which is displayed before the status code field.
# Fortunately, in this case, there is one.
# Maybe use a no-op prefix in the worst case (!)
PS1_base=$'\n'
# Functions for PROMPT_COMMAND
PS1_update_HISTCMD () {
# If HISTCONTROL contains "ignoredups" or "ignoreboth", this breaks.
# We should not change it programmatically
# (think principle of least astonishment etc)
# but we can always gripe.
case :$HISTCONTROL: in
*:ignoredups:* | *:ignoreboth:* )
echo "PS1_update_HISTCMD(): HISTCONTROL contains 'ignoredups' or 'ignoreboth'" >&2
echo "PS1_update_HISTCMD(): Warning: Please remove this setting." >&2 ;;
esac
# PS1_HISTCMD needs to contain the old value of PS1_HISTCMD2 (a copy of HISTCMD)
PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}
# PS1_HISTCMD2 needs to be unset for the next prompt to trigger properly
unset PS1_HISTCMD2
}
PROMPT_COMMAND=PS1_update_HISTCMD
# Finally, the actual prompt:
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
提示中的逻辑大致如下:
${PS1_base#foo...}
这将显示前缀。里面的东西#...
只对它的副作用有用。我们希望在不显示变量值的情况下进行一些变量操作,因此我们将它们隐藏在字符串替换中。(如果 ever 的值恰好以当前命令历史索引PS1_base
开头,这将显示奇怪且可能很壮观的事情。)foo
${PS1_HISTCMD2:=...}
这会分配一个值PS1_HISTCMD2
(如果它未设置,我们已经确定它是)。替换名义上也会扩展到新值,但我们已将其隐藏在 a${var#subst}
中,如上所述。
${HISTCMD%$PS1_HISTCMD}
我们将 的值HISTCMD
(当命令历史记录中的新条目创建时,即我们正在执行新命令时)或空字符串(当命令为空时)分配给PS1_HISTCMD2
。这通过修剪掉HISTCMD
任何匹配的值PS1_HISTCMD
(使用${var%subst}
后缀替换语法)来工作。
${_:+...}
这是从问题来的。$_
如果 的值被设置且非空(当执行命令时,但不是例如我们正在执行变量赋值时),它将扩展为 ... 一些东西。如果非空,“某物”应该是状态码(和一个空格,以便于阅读)PS1_HISTCMD2
。
${PS1_HISTCMD2:+$? }
那里。
'$ '
与原始问题一样,这只是实际的提示后缀。
所以关键部分是PS1_HISTCMD
记住先前值HISTCMD
的变量PS1_HISTCMD2
,以及捕获值的变量,HISTCMD
以便可以从内部访问PROMPT_COMMAND
,但需要取消设置,PROMPT_COMMAND
以便${PS1_HISTCMD2:=...}
下次提示时再次触发赋值显示。
我试图隐藏输出,${PS1_HISTCMD2:=...}
但后来意识到实际上我们想要显示一些东西,所以只是捎带它。您不能完全为空PS1_base
,因为 shell 显然会注意到,并且在没有值时甚至不会尝试执行替换;但是,如果您没有其他要显示的内容,也许您可以想出一个虚拟值(也许是无操作转义序列?)。或者也许可以将其重构为使用后缀运行;但这可能会更加棘手。
为了响应 Anubhava 的“最小答案”挑战,这里是没有注释或错误检查的代码。
PS1_base=$'\n'
PS1_update_HISTCMD () { PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}; unset PS1_HISTCMD2; }
PROMPT_COMMAND=PS1_update_HISTCMD
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ '
这可能不是最好的方法,但它似乎正在工作
function pc {
foo=$_
fc -l > /tmp/new
if cmp -s /tmp/{new,old} || test -z "$foo"
then
PS1='\n$ '
else
PS1='\n$? $ '
fi
cp /tmp/{new,old}
}
PROMPT_COMMAND=pc
结果
$ [ 2 = 2 ]
0 $ [ 2 = 3 ]
1 $
$
我需要使用很棒的脚本bash-preexec.sh。
尽管我不喜欢外部依赖项,但这是唯一可以帮助我避免1
在$?
按下enter
而不运行任何命令后进入的方法。
这将转到您的~/.bashrc
:
__prompt_command() {
local exit="$?"
PS1='\u@\h: \w \$ '
[ -n "$LASTCMD" -a "$exit" != "0" ] && PS1='['${red}$exit$clear"] $PS1"
}
PROMPT_COMMAND=__prompt_command
[-f ~/.bash-preexec.sh ] && . ~/.bash-preexec.sh
preexec() { LASTCMD="$1"; }
更新:后来我能够找到一个不依赖于 .bash-preexec.sh 的解决方案。