“如何解决错误”;巴什:!d';:“未找到事件”;Bash中的命令替换

“如何解决错误”;巴什:!d';:“未找到事件”;Bash中的命令替换,bash,parsing,sed,command-substitution,Bash,Parsing,Sed,Command Substitution,我试图解析VNC服务器启动事件的输出,但在命令替换中使用sed解析时遇到问题。具体而言,远程VNC服务器以如下方式启动: address1="user1@lxplus.cern.ch" VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)" New 'lxplus0186.cern.ch:1 (user1)' desktop is lxplus0186.cern.ch:1 Starting applications s

我试图解析VNC服务器启动事件的输出,但在命令替换中使用sed解析时遇到问题。具体而言,远程VNC服务器以如下方式启动:

address1="user1@lxplus.cern.ch"
VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)"
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
lxplus0186.cern.ch:1
VNCServerAndDisplayNumber="$(echo "${VNCServerResponse}" \
    | sed '/New.*desktop.*is/!d' | awk -F" desktop is " '{print $2}')"
然后对该启动事件中生成的标准错误输出进行分析,以提取服务器并显示信息。此时,变量
VNCServerResponse
的内容如下所示:

address1="user1@lxplus.cern.ch"
VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)"
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
lxplus0186.cern.ch:1
VNCServerAndDisplayNumber="$(echo "${VNCServerResponse}" \
    | sed '/New.*desktop.*is/!d' | awk -F" desktop is " '{print $2}')"
可以通过以下方式解析此输出,以提取服务器和显示信息:

echo "${VNCServerResponse}" | sed '/New.*desktop.*is/!d' \
    | awk -F" desktop is " '{print $2}'
结果如下:

address1="user1@lxplus.cern.ch"
VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)"
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
lxplus0186.cern.ch:1
VNCServerAndDisplayNumber="$(echo "${VNCServerResponse}" \
    | sed '/New.*desktop.*is/!d' | awk -F" desktop is " '{print $2}')"
我要做的是在命令替换中使用此解析,如下所示:

address1="user1@lxplus.cern.ch"
VNCServerResponse="$(ssh "${address1}" 'vncserver' 2>&1)"
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
lxplus0186.cern.ch:1
VNCServerAndDisplayNumber="$(echo "${VNCServerResponse}" \
    | sed '/New.*desktop.*is/!d' | awk -F" desktop is " '{print $2}')"
尝试执行此操作时,我遇到以下错误:

bash: !d': event not found
我不知道如何解决这个问题。在命令替换中使用sed的方式似乎存在问题。我希望得到指导。

不要将
$(…)
命令替换用双引号括起来。您正在要求shell对引号的内容执行求值,并且正在使用历史替换扩展功能。去掉引号,你就不再告诉shell这样做,你就不会碰到那个问题

而且,即使输出可能包含空格或换行符或其他内容,在该赋值行上删除这些引号也是安全的。这种类型的赋值不会像命令替换或变量求值在正常shell执行行中那样进行拆分


或者,在运行shell/脚本之前,在其中禁用历史扩展。(我相信在默认情况下运行脚本时它应该关闭。)

问题是,在双引号中,bash试图展开
!d
,然后将其传递到子shell。您可以通过删除双引号来解决此问题,但我还建议简化您的脚本:

VNCServerAndDisplayNumber=$(echo "$VNCServerResponse" | awk '/desktop/ {print $NF}')
这只是打印包含单词“desktop”的行上的最后一个字段

在较新的bash上,您可以使用herestring,而不是管道式的
回音

VNCServerAndDisplayNumber=$(awk '/desktop/ {print $NF}' <<<"$VNCServerResponse")

VNCServerAndDisplayNumber=$(awk'/desktop/{print$NF}')只有在启用历史扩展时才会发生这种情况,而历史扩展通常不适用于脚本,也绝对不应该用于脚本

与其尝试解决它,不如弄清楚为什么启用了历史扩展,以及为什么不启用历史扩展

  • 如果使用
    .foo
    源代码foo
    执行脚本,请改用
    /foo

  • > p>如果您在 BasHC 中编写此函数,请考虑将其作为单独的脚本。

  • 如果您的脚本(或BASH_ENV)显式地执行了
    设置-H
    ,请不要执行


  • Bash历史扩展在Bash命令行解析器中是一个非常奇怪的角落,您显然遇到了意外的历史扩展,如下所述。但是,脚本中的任何类型的历史扩展都是意外的,因为通常在脚本中不启用历史扩展;甚至连使用
    源代码运行的脚本也不例外(或
    )内置

    如何启用(或禁用)历史扩展 有两个shell选项可控制历史扩展:

    • 设置-o历史记录
      :需要记录历史记录

    • set-H
      (或
      set-o histexpand
      ):要启用历史扩展,还需要额外设置

    必须设置这两个选项,才能识别历史扩展。(我发现手册中对该交互不清楚,但它足够符合逻辑。)

    根据bash手册,这些选项对于非交互式Shell是未设置的,因此如果您想在脚本中启用历史扩展(我无法想象您希望这样做的原因),则需要同时设置这两个选项:

    set -o history -o histexpand
    
    # 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
    
    使用
    source
    运行脚本的情况更为复杂(我将要说的仅适用于bash v4,因为它在中未记录,将来可能会发生变化)。[注3]

    历史记录(以及随后的扩展)在
    source
    'd脚本中被关闭,但据我所知,它是通过一个不可见的内部标志来关闭的。它肯定不会出现在
    $SHELLOPTS
    中。由于
    source
    d脚本在当前bash上下文中运行,它共享当前的执行环境,包括shell选项。因此在的执行中de>source
    d从交互式会话启动的脚本,您将在
    $SHELLOPTS
    中看到
    历史记录
    histexpand
    ,但不会进行历史记录扩展。要启用它,您需要:

    set -o history
    
    这不是一个无操作,因为它具有重置内部标志的副作用,该标志会抑制历史记录。设置
    histexpand
    shell选项不会产生此副作用

    简而言之,我不确定您是如何在脚本中实现历史扩展的(如果,实际上,错误的命令是在脚本中而不是在交互shell中),但是您可能会考虑不这样做,除非您有一个很好的理由。 如何解析历史扩展 历史扩展的bash实现设计用于
    readline
    ,因此可以在命令输入期间执行。(默认情况下,此函数绑定到Meta-^;通常Meta是ESC,但也可以自定义。)但是,它也会在输入每一行之后,在执行任何bash解析之前立即执行

    默认情况下,历史扩展字符为!并且(正如大多数文档所述)将触发历史扩展,除了:

  • 当后跟空格或=

  • 如果设置了shell选项
    extglob
    ,并且后跟([注1]

    <