【问题标题】:Detect empty command检测空命令
【发布时间】:2015-02-07 16:30:23
【问题描述】:

考虑一下这个 PS1

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

这是几个命令的结果

$ [ 2 = 2 ]

0 $ [ 2 = 3 ]

1 $

1 $

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

【问题讨论】:

  • DEBUG 陷阱也不会在空命令行上触发。
  • @chepner trap 'echo hello' DEBUGhello 每次我按回车键。
  • 嗯,如果PROMPT_COMMAND 执行命令,它只会在空行上为我触发,在这种情况下它似乎会触发两次。 (一次用于空命令,一次用于通过PROMPT_COMMAND 执行的每个命令。)
  • 我设置了PROMPT_COMMANDecho hi 在三行上给出 hellohihello。按回车只会给hello。我觉得DEBUG 陷阱可能会回答这个问题,但我不确定。
  • 它似乎只是保持以前的状态进入。

标签: bash shell interactive ps1


【解决方案1】:

我需要使用出色的脚本bash-preexec.sh

虽然我不喜欢外部依赖项,但这是唯一能帮助我避免在不运行任何命令的情况下按 enter 后在 $? 中出现 1 的方法。

这将发送给您的~/.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"; }

更新:后来我找到了solution without dependency on .bash-preexec.sh

【讨论】:

  • +1 以获得干净的解决方案。不使用外部依赖是愚蠢的。然后你也可以卸载所有东西,扔掉电脑,创造你自己的……一切。
  • 一切都取决于你实现外部依赖的难易程度。使用与您类似的另一个极端“发明自己的”:gcc 拒绝用 python lwn.net/Articles/760702 替换 awk(每个人都更愿意维护 python 脚本而不是 awk 脚本,但是将 python 作为交叉编译器依赖项很难)。 + 具有外部依赖项,您需要保持它的兼容性(它可能会在下一个主要 bash 版本中中断)。
【解决方案2】:

这是一个有趣、非常简单的可能性:它使用 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[\#]=}\$ '

【讨论】:

  • @StevenPenny 也许您正在寻找一种机制来打开和关闭返回码。如果是这种情况,请考虑:PS1='\n${__cmdnbary[\#]-${__hide_prompt_ret-$? }}${__cmdnbary[\#]=}\$ '。然后你只需要设置(到空字符串)或unset变量__hide_prompt_ret来显示或隐藏返回码。
  • 应该注意,这会使用越来越多的内存。也许微不足道,但仍然如此。
【解决方案3】:

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

将这些行放在~/.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 内部 var BASH_COMMAND 将变量 _cmd 设置为实际执行的命令。
  • PROMPT_COMMAND 钩子将 _ret 设置为 "$? " 如果 _cmd 不为空,则将 _ret 设置为 ""。最后它将_cmd var 重置为空状态。

【讨论】:

  • 不,您的~/.bashrc 之后可能还有其他命令运行。如果您没有~/.bash_profile,请仅使用此代码块创建一个,看看是否会改变行为。要进行调试,您可以在 trap "" DEBUG; 之后的 PROMPT_COMMAND= 行中插入 echo $_cmd
  • 我在测试中没有看到0 $。你能按照我建议的方式调试它,以便打印extra 命令。也更好地测试它直接复制/粘贴到您的~/.bash_profile。当这对你有用时,我会将其转换为函数。
  • 啊,这是我怀疑的,因为上面的当前表格效果很好。我目前在手机上,等我回来我会将PROMPT_COMMAND 转换为函数pc 并更新答案。
【解决方案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 的值(当在命令历史记录中创建新条目时,即我们正在执行新命令时)或空字符串(当命令为空时)分配给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 的“最小答案”挑战,这里是没有 cmets 或错误检查的代码。

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:+$? }}$ '

【讨论】:

    【解决方案5】:

    这可能不是最好的方法,但它似乎有效

    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 $
    
    $
    

    【讨论】:

    • 您似乎对变量_ 非常感兴趣。但是,此变量扩展为最后一个命令的最后一个参数。因此,当最后一个参数为空时,您的代码不会按预期运行,例如,echo ''。此外,如果您发出一行 cmets,这不会按预期运行。此外,使用您的方法,重复命令不会显示预期的行为。
    猜你喜欢
    • 2022-01-01
    • 1970-01-01
    • 2021-01-22
    • 2021-03-23
    • 2017-12-01
    • 2012-02-03
    • 1970-01-01
    • 2013-03-31
    • 2019-10-03
    相关资源
    最近更新 更多