如何让 Tcl 的 exec 执行 运行 一个命令,其参数用空格引用字符串?

How do I get Tcl's exec to run a command whose arguments have quoted strings with spaces?

我想使用 pgrep 从命令行查找进程的 pid。在shell中,这样做是这样的:

pgrep -u andrew -fx 'some_binary -c some_config.cfg'

但是当我从 Tcl 尝试这个时,像这样:

exec pgrep -u $user -fx $cmdLine

我得到:

pgrep: invalid option -- 'c'

这是有道理的,因为它看到了这个:

pgrep -u andrew -fx some_binary -c some_config.cfg

但是我加上单引号也是一样的:

exec pgrep -u $user -fx '$cmdLine'

这也是有道理的,因为单引号对 Tcl 来说并不特殊。我认为它认为 'some_binary 是一个参数,然后是 -c,然后是 some_config.cfg'

我也试过:

exec pgrep -u $user -fx {$cmdLine}

set cmd "pgrep -u $user -fx '$cmdLine'"
eval exec $cmd

无济于事。

根据我的阅读,似乎 Tcl 8.5+ 中的 {*} 功能可能有所帮助,但我公司的基础架构运行 Tcl 8.0.5。

由于您使用的是旧版本,因此您必须确保 eval 看到的内容将被转换为正确引用的 Tcl 字符串。

单引号什么都不做。 exec 没有使用它们,也没有传递它们。 exec 利用底层的 exec(3) 系统调用,除非您有意使用类似以下的内容,否则不会进行任何参数解释: /bin/sh -c "some-cmd some-arg" 其中调用 shell 并将重新解释命令行.

您需要做的是构造一个字符串,eval 将其解释为带引号的 Tcl 字符串。对于这些结构,您可以使用“{part1 part2}”或“\"part1 part2\"”。

首先,一个小测试脚本来验证参数是否正确传递:

#!/bin/bash
for i in "$@"; do
  echo $i
done

然后是 Tcl 脚本:

#!/usr/bin/tclsh
exec ./t.sh -u andrew -fx "some_binary -c some_config.cfg" >@ stdout
eval exec ./t.sh -u andrew -fx "{some_binary -c some_config.cfg}" \
     >@ stdout
eval exec ./t.sh -u andrew -fx "\"some_binary -c some_config.cfg\"" \
     >@ stdout
# the list will be converted to a string that is already properly
# quoted for interpretation by eval.
set cmd [list ./t.sh -u andrew -fx "some_binary -c some_config.cfg"]
eval exec $cmd >@ stdout

部分原因是 ' 对 Tcl 毫无意义,部分原因是您失去了对单词边界位置的控制。


首先,仔细检查这是否有效:

exec pgrep -u $user -fx "some_binary -c some_config.cfg"

或者也许这个(Tcl 使用 {} 就像 Unix shell 使用单引号但具有可嵌套的额外好处;这就是大括号 really 在 Tcl 中执行):

exec pgrep -u $user -fx {some_binary -c some_config.cfg}

应该起作用的是:

set cmdLine "some_binary -c some_config.cfg"
exec pgrep -u $user -fx $cmdLine

您将 cmdLine 设置为 的位置 您想要的字符(如果您不确定,请打印出来检查;重要的是变量中的值,而不是您在脚本中编写的引用版本)。我将使用下面的 set cmdLine "…" 表格,但实际上使用您需要的任何东西才能正常工作。

现在,如果你要通过这个过去 eval,那么你应该使用 list 添加额外的引号,以确保安全:

set cmdLine "some_binary -c some_config.cfg"
set cmd [list pgrep -u $user -fx $cmdLine]
eval exec $cmd

list 命令生成列表,但它使用 规范形式 这也是 保证的脚本片段 缺少“惊喜”替换或单词边界。


如果您使用的是较新版本的 Tcl(特别是 8.5 或更高版本),则可以使用扩展。这旨在 专门 list 一起很好地工作,并且在大约 99% 的情况下无需使用 eval。这会改变:

eval exec $cmd

进入:

exec {*}$cmd

语义有点不同 除了 cmd 持有规范列表时,它们实际上 运行 相同的操作。 (当你处理非规范列表时,差异就出现了,其中 eval 会做各种各样的事情——想象一下 set cmd {ab [exit] cd} 的破坏,这是一个有效但非规范的列表——而扩展只是强制事情是一个列表,并使用列表中的单词而不作进一步解释。)