检测空命令

时间:2014-12-09 17:10:49

标签: bash shell interactive ps1

考虑这个PS1

PS1='\n${_:+$? }$ '

这是几个命令的结果

$ [ 2 = 2 ]

0 $ [ 2 = 3 ]

1 $

1 $

第一行显示没有预期的状态,接下来的两行显示 正确的退出代码。但是在第3行只有Enter被按下,所以我想要 状态消失,如第1行。我该怎么做?

5 个答案:

答案 0 :(得分:17)

这是一个有趣,非常简单的可能性:它使用\#转义序列PS1以及参数扩展(以及Bash扩展其提示的方式)。

转义序列\#扩展为要执行的命令的命令编号。每次实际执行命令时,这都会增加。试试吧:

$ PS1='\# $ '
2 $ echo hello
hello
3 $ # this is a comment
3 $
3 $    echo hello
hello
4 $

现在,每次显示提示时,Bash首先展开PS1中找到的转义序列,然后(如果设置了shell选项promptvars,这是默认值),此字符串通过参数扩展,命令替换,算术扩展和引用删除来扩展。

然后,只要( k -1)-th命令,就有了一个数组,该数组将 k -th字段设置为空字符串被执行。然后,使用适当的参数扩展,我们将能够检测何时设置这些字段,并在未设置字段时显示上一个命令的返回码。如果要调用此数组__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[\#]=}\$ '

答案 1 :(得分:7)

这个周末有时间玩这个游戏。看看我之前的答案(不好)和其他答案,我认为这可能是最小的答案。

将这些行放在~/.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' DEBUGPROMPT_COMMAND挂钩。
  • PS1正在使用变量_ret,即PS1='$_ret$ '
  • trap命令仅在执行命令时运行,但即使按下空输入也会运行PROMPT_COMMAND
  • trap命令使用BASH internal var _cmd为实际执行的命令设置变量BASH_COMMAND
  • 如果PROMPT_COMMAND非空,则
  • _ret"$? "设置为_cmd,否则将_ret设置为""。最后,它将_cmd var重置为空状态。

答案 2 :(得分:4)

每次执行新命令时,都会更新变量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...}

显示前缀。 #...中的内容仅对其副作用有用。我们想要在不显示变量值的情况下进行一些变量操作,因此我们将它们隐藏在字符串替换中。 (如果PS1_base的值始终以foo开头,后跟当前的命令历史索引,这将显示奇怪且可能很壮观的事情。)

${PS1_HISTCMD2:=...}

这会为PS1_HISTCMD2分配一个值(如果未设置,我们已确定它是)。替换名义上也会扩展到新值,但我们已将其隐藏在${var#subst}中,如上所述。

${HISTCMD%$PS1_HISTCMD}

我们分配HISTCMD的值(当命令历史记录中的新条目,即我们正在执行新命令时)或空字符串(当命令为空时)到{{1 }}。这可以通过修剪PS1_HISTCMD2HISTCMD的任何匹配值(使用PS1_HISTCMD后缀替换语法)来实现。{/ p>

${var%subst}

这是问题所在。如果设置${_:+...} 的值并且非空(这是在执行命令时,但是例如我们正在执行变量赋值),它将扩展为......。 &#34;东西&#34;如果$_非空,则应为状态代码(以及可读性的空格)。

PS1_HISTCMD2

${PS1_HISTCMD2:+$? }

这只是实际的提示后缀,与原始问题一样。

因此,关键部分是变量'$ ' ,它们会记住PS1_HISTCMD的先前值,而变量HISTCMD会捕获PS1_HISTCMD2的值,因此可以访问它从HISTCMD开始,但需要在PROMPT_COMMAND中取消设置,以便下次显示提示时再次触发PROMPT_COMMAND作业。

我试图隐藏${PS1_HISTCMD2:=...}的输出,但后来意识到实际上有一些我们想要显示的东西,所以只是捎带它。你不能拥有一个完全空的${PS1_HISTCMD2:=...},因为shell显然会注意到,并且在没有值时甚至不会尝试执行替换;但也许你可以想出一个虚拟值(一个无操作的转义序列,也许?)如果你没有其他你想要显示的东西。或者也许这可以重构为以后缀运行;但这可能会更加棘手。

回应Anubhava&#34;最小的答案&#34;挑战,这里是没有评论或错误检查的代码。

PS1_base

答案 3 :(得分:3)

这可能不是最好的方法,但它似乎正在运作

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 $

$

答案 4 :(得分:0)

我需要使用优秀的剧本 bash-preexec.sh

虽然我不喜欢外部依赖,但这是唯一可以帮助我在1之后$?避免在没有运行任何命令的情况下enter。< / p>

这是您的~/.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"; }