Bash 调用 windows 程序,引用如何工作?

Bash calling windows program, how does quoting work?

我正在为我的虚拟机编写一些脚本,我正在使用 cygwin。我需要设置计算机名称和IP地址。这部分很简单,顺序是:

wmic computersystem where caption=name "vm-01"
netsh interface ip set address "Local Area Connection 2" static 10.155.155.50 255.255.255.0

这在 cmd.exe 中工作得很好。现在我想从 bash 执行它。我想查看我正在执行的命令,所以我正在使用这个 bash 函数来执行它:

call() {
    echo "$@"
    $@
}

我以直观的方式尝试了转义引号:

$ call wmic computersystem where caption=name \"wm-01\"
Executing (\BOH\ROOT\CIMV2:Win32_ComputerSystem.Name="XXX")->rename()
Method execution successful.
Out Parameters:
instance of __PARAMETERS
{
        ReturnValue = 87;
};

哪个不起作用(不要像我一开始那样被 "Method execution successful" 误导),错误代码 87。

$ call netsh interface ip set address \"Local Area Connection 2\" static 10.155.155.50 255.255.255.0

另一方面,这很管用。

我设法通过

wmic 命令解决了这个问题
$ call wmic computersystem where caption=name \'wm-01\'
Executing (\BOH\ROOT\CIMV2:Win32_ComputerSystem.Name="XXX")->rename()
Method execution successful.
Out Parameters:
instance of __PARAMETERS
{
        ReturnValue = 0;
};

这确实有效。我用 netsh

做了同样的尝试
$ call netsh interface ip set address \'Local Area Connection 2\' static 10.155.155.101 255.255.255.0
The filename, directory name, or volume label syntax is incorrect.

这不是。我想了解的是为什么一个命令需要 \' 而第二个命令需要 \"?

这里有几点重要的是要认识到:

  • 引用的意义是 shell,而不是(通常)正在启动的命令。 shell 在识别命令名称和传递其参数之前,但在所有其他命令行处理之后删除了为此目的而重要的引号。

  • 只有 命令 的原始文本中出现的未加引号对 shell 的引用才有意义。特别是,参数扩展产生的引号字符并不特殊——它们只是代表它们自己。

  • 虽然 shell 在执行任何扩展之前将命令行拆分为标记,但它稍后会在扩展命令上执行单词拆分。在已识别的引号内出现的扩展不会被分词,但是,同样地,来自 扩展的引号不会被识别为特殊的;他们不防止分词。

然后,考虑一下您的命令的这个版本:

call netsh interface ip set address "Local Area Connection 2" static 10.155.155.50 255.255.255.0

bash 读取该行,将其拆分为单词,平凡地执行扩展,执行单词拆分,然后执行引用删除,得到这些单词:

  • 通话
  • netsh
  • 界面
  • ip
  • 设置
  • 地址
  • 本地连接2
  • 静态
  • 10.155.155.50
  • 255.255.255.0

请注意 "Local Area Connection 2"(不带引号)是一个 'word'。第一个单词 'call' 是命令,其余单词是参数,由 $@ 表示(也由 $* 和各个位置参数 </code> 表示, <em>etc</em>.) 内部函数 <code>call().

现在考虑一下当 call() 开始执行命令时会发生什么:在单词拆分之前,它与之前大致相同,但在那里,连接名称被拆分为多个参数,并显示netsh 命令的方式。

我相信你已经很感激了,但现在考虑一下你的命令的这个变化:

call netsh interface ip set address \"Local Area Connection 2\" static 10.155.155.50 255.255.255.0

在这种情况下," 个字符被转义,因此不能达到使连接名称成为单个 shell 单词的目的。因此,它甚至在达到 call() 之前就已经分裂了。因此,您得到与以前相同的结果,只是 netsh 的两个参数中包含引号字符。

那么解决方法是什么?你几乎已经明白了。特殊参数 $@$* 之间的区别在于它们与分词的交互。当 $@ 在引号内扩展时,它是不对结果进行单词拆分的规则的一个特殊例外——结果在 $@ 的元素之间拆分,而不是 他们之中。主要目的正是您要尝试做的事情:将 shell 的位置参数传递给另一个命令。

换句话说,使用这个版本的 shell 函数:

call() {
  echo $@
  "$@"
}

并使用您原来的命令行调用它,使用不带引号的引号。