gpt4 book ai didi

bash - 如何解决错误 "bash: !d' : event not found"in Bash command substitution

转载 作者:行者123 更新时间:2023-11-29 09:01:21 25 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





echo "#!" fails -- "event not found"

(5 个回答)


6年前关闭。




我正在尝试解析 VNC 服务器启动事件的输出,但在命令替换中使用 sed 进行解析时遇到了问题。具体来说,远程VNC服务器的启动方式如下:

address1="user1@lxplus.cern.ch"
VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)"

然后将解析此启动事件中产生的标准错误输出,以提取服务器和显示信息。此时变量 VNCServerResponse的内容是这样的:
New 'lxplus0186.cern.ch:1 (user1)' desktop is lxplus0186.cern.ch:1

Starting applications specified in /afs/cern.ch/user/u/user1/.vnc/xstartup
Log file is /afs/cern.ch/user/u/user1/.vnc/lxplus0186.cern.ch:1.log

可以通过以下方式解析此输出,以提取服务器并显示信息:
echo "${VNCServerResponse}" | sed '/New.*desktop.*is/!d' \
| awk -F" desktop is " '{print $2}'

结果如下所示:
lxplus0186.cern.ch:1

我想要做的是在命令替换中使用此解析,如下所示:
VNCServerAndDisplayNumber="$(echo "${VNCServerResponse}" \
| sed '/New.*desktop.*is/!d' | awk -F" desktop is " '{print $2}')"

在尝试执行此操作时,出现以下错误:
bash: !d': event not found

我不知道如何解决这个问题。在命令替换中使用 sed 的方式似乎存在问题。我将不胜感激指导。

最佳答案

Bash 历史扩展是 bash 命令行解析器中一个非常奇怪的角落,您显然遇到了意外的历史扩展,下面将对此进行解释。但是,脚本中的任何类型的历史扩展都是意外的,因为通常脚本中没有启用历史扩展;甚至脚本都不能与 source 一起运行(或 . )内置。

如何启用(或禁用)历史扩展

有两个 shell 选项可以控制历史扩展:

  • set -o history : 需要记录历史记录。
  • set -H (或 set -o histexpand ):启用历史扩展还需要。

  • 必须设置这两个选项才能识别历史扩展。 (我发现手册中关于这种交互的内容不清楚,但它是合乎逻辑的。)

    根据 bash 手册,这些选项对于非交互式 shell 是未设置的,所以如果你想在脚本中启用历史扩展(我无法想象你想要这个的原因),你需要设置它们:
    set -o history -o histexpand

    脚本运行的情况 source更复杂(我将要说的仅适用于 bash v4,而且由于它没有记录在 future 可能会改变)。 [注3]

    source 中关闭历史记录(以及随之而来的扩展) 'd 脚本,但通过一个内部标志,据我所知,它是不可见的。它肯定不会出现在 $SHELLOPTS 中.自 source d 脚本在当前 bash 上下文中运行,它共享当前的执行环境,包括 shell 选项。所以在执行一个 source从交互式 session 启动的 d 脚本,您将看到 historyhistexpand$SHELLOPTS ,但不会发生历史扩展。为了启用它,您需要:
    set -o history

    这不是空操作,因为它具有重置抑制历史记录的内部标志的副作用。设置 histexpand shell 选项没有这种副作用。

    简而言之,我不确定您是如何设法在脚本中启用历史扩展的(如果确实,行为不当的命令是在脚本中而不是在交互式 shell 中),但您可能想考虑不这样做,除非您有一个很好的理由。

    如何解析历史扩展

    历史扩展的 bash 实现旨在与 readline 一起使用,以便它可以在命令输入期间执行。 (默认情况下,此函数绑定(bind)到 Meta-^;通常 Meta 是 ESC,但您也可以自定义它。)但是,它也会在每行输入后立即执行,在执行任何 bash 解析之前。

    默认情况下,历史扩展字符是 !,并且——正如大多数文档所描述的——将触发历史扩展,除了:
  • 当后面跟着空格或 =
  • 如果 shell 选项 extglob已设置,后跟 ( [注 1]
  • 如果它出现在单引号字符串中
  • 如果前面有\[注 2,见下文]
  • 如果前面有 $ 或 ${ [注 1]
  • 如果前面有 [ [注 1]
  • (从 bash v4.3 开始)如果它是双引号字符串中的最后一个字符。

  • 这里的直接问题是对第三种情况的精确解释,一个 !出现在单引号字符串中。通常, bash为命令替换启动一个新的引用上下文( $(...) 或已弃用的反引号表示法)。例如:
    $ s=SUBSTITUTED
    $ # The interior single quotes are just characters
    $ echo "'Echoing $s'"
    'Echoing SUBSTITUTED'
    $ # The interior single quotes are single quotes
    $ echo "$(echo 'Echoing $s')"
    Echoing $s

    然而,历史扩展扫描器并不是那么智能。它跟踪引号,但不跟踪命令替换。所以就其而言,上面例子中的两个单引号都是双引号单引号,也就是普通字符。所以历史扩展发生在它们两个中:
    # A no-op to indicated history expansion
    $ HIST() { :; }
    # Single-quoted strings inhibit history expansion
    $ HIST
    $ echo '!!'
    !!
    # Double-quoted strings allow history expansion
    $ HIST
    $ echo "'!!'"
    echo "'HIST'"
    'HIST'
    # ... and it applies also to interior command substitution.
    $ HIST
    $ echo "$(echo '!!')"
    echo "$(echo 'HIST')"
    HIST

    所以如果你有一个完全正常的命令,比如 sed '/foo/!d' file ,您希望单引号可以保护您免受历史扩展,并将其放入双引号命令替换中:
    result="$(sed '/foo/!d' file)"

    你突然发现了!是一个历史扩展字符。更糟糕的是,您无法通过反斜杠转义感叹号来解决此问题,因为尽管 "\!"禁止历史扩展,它不会删除反斜杠:
    $ echo "\!"
    \!

    在这个特定的例子中——以及 OP 中的那个——双引号是完全没有必要的,因为变量赋值的右侧既没有经历文件名扩展也没有分词。但是,在其他情况下,删除双引号会改变语义:
    # Undesired history expansion
    printf "The answer is '%s'\n" "$(sed '/foo/!d' file)"

    # Undesired word splitting
    printf "The answer is '%s'\n" $(sed '/foo/!d' file)

    在这种情况下,最好的解决方案可能是将 sed 参数放在一个变量中
    # Works
    sed_prog='/foo/!d'
    printf "The answer is '%s'\n" "$(sed "$sed_prog" file)"

    (在这种情况下, $sed_prog 周围的引号不是必需的,但通常它们是必需的,并且它们没有害处。)

    备注:
  • 当后面的字符是某种形式的开括号时,历史扩展的抑制只有在字符串的其余部分有相应的右括号时才起作用。但是,它不必真正匹配左括号。例如:
    # No matching close parenthesis
    $ echo "!("
    bash: !: event not found
    # The matching close parenthesis has nothing to do with the open
    $ echo "!(" ")"
    !( )
    # An actual extended glob: files whose names don't start with a
    $ echo "!(a*)"
    b
  • bash 中所示手册中,如果前面紧跟反斜杠,历史扩展字符将被视为普通字符。这确实是真的。反斜杠以后是否会被视为转义字符并不重要:
    $ echo \!
    !
    $ echo \\!
    \!
    $ echo \\\!
    \!

    \也禁止双引号内的历史扩展,但 \!不是双引号字符串中的有效转义序列,因此不会删除反斜杠:
    $ echo "\!"
    \!
    $ echo "\\!"
    \!
    $ echo "\\\!"
    \\!
  • 我在撰写本文时指的是 bash v4.2 的源代码,因此任何未记录的行为可能与 v4.3 完全不同。
  • 关于bash - 如何解决错误 "bash: !d' : event not found"in Bash command substitution,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25003162/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com