有没有办法防止 Windows 命令行中 env 变量的百分比扩展?

jak*_*b.g 4 windows cmd variable-expansion node.js

我在 Windows 上的 git bash 中使用以下 git 命令:

git log --format="%C(cyan)%cd%Creset %s" --date=short -5
Run Code Online (Sandbox Code Playgroud)

它显示提交日期 ( %cd),后跟提交消息 ( %s)。提交日期用颜色标记包裹:%C(cyan)开始彩色输出和%Creset停止彩色输出。

虽然它在 git bash 中运行良好,但它不适用于cmd:%cd%被 Windows shell 扩展到当前工作目录(相当于$PWD在 bash 中)。

因此,当该命令通过 运行时cmd,我看到当前工作目录显示而不是第一列中的提交日期! git bash:

2015-10-08 commit msg
2015-10-08 commit msg
2015-10-07 commit msg
2015-10-06 commit msg
2015-10-06 commit msg
Run Code Online (Sandbox Code Playgroud)

指令:

D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg
D:\git\someFolderCreset commit msg
Run Code Online (Sandbox Code Playgroud)

实际上,我cmd自己从来没有直接使用过,我在编写 nodejs (0.12) 脚本时发现了这种行为,其中有

require('child_process').execSync('git log --format=...', {stdio: 'inherit'})
Run Code Online (Sandbox Code Playgroud)

它由节点使用cmdWindows 上的when执行)。

一个简单的解决方法可能是引入一个空间以防止%cd%被发现,即更改

git log --format="%C(cyan)%cd%Creset %s" --date=short -5
Run Code Online (Sandbox Code Playgroud)

git log --format="%C(cyan)%cd %Creset%s" --date=short -5
Run Code Online (Sandbox Code Playgroud)

然而,这引入了一个冗余空间(我之前删除了另一个空间,%s但它仍然是一个黑客,需要手动干预)。

有没有办法防止 Windows shell 扩展?

找到了有关使用%%^%转义的信息,%但它们不是这里的解决方案:

# produces superfluous ^ characters
git log --format="%C(cyan)^%cd^%Creset %s" --date=short -5

^2015-10-08^ commit msg
^2015-10-08^ commit msg
^2015-10-07^ commit msg
^2015-10-06^ commit msg
^2015-10-06^ commit msg

# seems the expansion is done at command parse time
git log --format="%C(cyan)%%cd%%Creset %s" --date=short -5

%D:\git\someFolder commit msg
%D:\git\someFolder commit msg
%D:\git\someFolder commit msg
%D:\git\someFolder commit msg
%D:\git\someFolder commit msg
Run Code Online (Sandbox Code Playgroud)

理想的解决方案应该与 bash 和 cmd 兼容,不会产生多余的字符,或者在 javascript 中使用转义函数来转义 Windows 的通用 UNIX-y 命令以防止扩展(如果可以创建这样的转义函数)。

mkl*_*nt0 6

提供MC ND 有用答案的替代方案:

如果您真的需要shell参与其中(这不太可能,因为您声明希望该命令同时适用于 Windowscmd.exe Bash),请立即考虑下面的解决方案;有关绕过该问题的无替代方案,请参阅底部的解决方案。

MC ND 致敬,通过建议在%实例周围放置双引号而不是实例之间 的潜在变量名称来改进我的原始方法%,并建议澄清 re execFileSync


“转义”%字符。为了cmd.exe

正如MC ND 的回答中所述,从技术上讲,您无法%在 Windows 命令提示符处转义(在您可以使用的批处理文件中%%,但是在从其他环境(例如 Node.js)调用 shell 命令时这不起作用,并且通常无法跨平台)。

但是,解决方法在每个%实例周围放置双引号

// Input shell command.
var shellCmd = 'git log --format="%C(cyan)%cd%Creset %s" --date=short -5'

// Place a double-quote on either end of each '%'
// This yields (not pretty, but it works):
//   git log --format=""%"C(cyan)"%"cd"%"Creset "%"s" --date=short -5
var escapedShellCmd = shellCmd.replace(/%/g, '"%"')

// Should work on both Windows and Unix-like platforms:
console.log(require('child_process').execSync(escapedShellCmd).toString())
Run Code Online (Sandbox Code Playgroud)

插入的双引号阻止cmd.exe识别诸如%cd%变量引用之类的标记("%"cd"%"不会被扩展)。

这是有效的,因为在目标程序处理时,额外的双引号最终会从字符串中删除:

  • Windows :(git.exe大概是通过 C 运行时)然后负责从组合字符串中去除额外的双引号。

  • 类 Unix(类POSIX 外壳,例如 Bash):外壳本身负责在将双引号传递给目标程序之前删除双引号。

    • 警告:在您的命令中使用双引号通常意味着您需要注意类似 POSIX 的 shell 在$-prefixed 标记上执行可能不需要的扩展(这里不是问题);但是,为了保持与 Windows 兼容,您必须使用双引号。

从技术上讲,将此技术应用于双引号字符串将其分解为一系列双引号子字符串,其中散布着未引用的 %实例。类似于 POSIX 的 shell 仍然将其识别为单个字符串 - 子字符串被双引号括起来并直接与%实例相邻。(如果您将该技术应用于未加引号的字符串,则逻辑相反:您实际上是在双引号%实例中进行拼接。)子字符串周围的双引号被视为语法元素而不是字符串的一部分,然后当子字符串连接在一起形成单个文字以传递给目标程序时删除。


通过完全避免外壳来绕过问题

注意:以下内容基于execFile[Sync],它仅适用于调用外部可执行文件(在 OP 的情况下为真:)git.exe - 相比之下,对于调用shell 内置命令(内部命令)或 Windows批处理文件,您无法避免exec[Sync]并因此解释由cmd.exe(on视窗)。[1]

如果您使用execFileSync而不是execSync则不会涉及外壳(cmd.exe在 Windows 上),因此您不必担心转义%字符。或任何其他 shell 元字符,就此而言:

require('child_process').execFileSync('git', 
   [ 'log', 
     '--format=%C(cyan)%cd%Creset %s',
     '--date=short',
     '-5' ], {stdio: 'inherit'})
Run Code Online (Sandbox Code Playgroud)

注意如何的参数,必须提供单独作为一个元素阵列,并且没有嵌入引用


[1] 在 Windows 上,不能直接使用 调用脚本文件(例如 Python 脚本)execFile[Sync],但您可以将解释器可执行文件(例如python)作为要执行的文件传递,并将脚本文件作为参数传递。在类 Unix 平台上,您可以直接调用脚本,只要它们有一个shebang 行并且被标记为可执行文件。
execFile[Sync] 用于调用 Windows批处理文件,但cmd.exe总是插入参数,就像exec[Sync].