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

时间:2015-10-08 12:40:06

标签: node.js windows cmd variable-expansion

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

git log --format="%C(cyan)%cd%Creset %s" --date=short -5

它显示提交日期(%cd),后跟提交消息(%s)。提交日期包含颜色标记:%C(cyan)以启动彩色输出,%Creset以停止彩色输出。

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

因此,当该命令通过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

CMD:

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

实际上,我从不直接使用cmd,我在编写nodejs(0.12)脚本时发现了这种行为

require('child_process').execSync('git log --format=...', {stdio: 'inherit'})

在Windows上使用cmd由节点执行。)

一个简单的解决方法可能是引入一个空格来阻止找到%cd%,即更改

git log --format="%C(cyan)%cd%Creset %s" --date=short -5

git log --format="%C(cyan)%cd %Creset%s" --date=short -5

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

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

我有found有关使用%%^%来逃避%的信息,但它们不是解决方案:

# 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

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

2 个答案:

答案 0 :(得分:4)

提供MC ND's helpful answer的替代方案:

如果您确实需要来获取 shell (这不太可能,因为您声明您希望命令同时使用Windows' cmd.exe Bash),请考虑下面的解决方案;对于绕过问题的 无shell 备选方案,请参阅底部的解决方案。

通过建议在%个实例周围放置双引号而不是 {{之间的潜在变量名称​​来改进原始方法的提示[/ 3}} 1}}个实例,并建议澄清重新%

"逃逸" execFileSync字符。对于%

MC ND中所述,您无法在Windows命令提示符处(在cmd.exe批处理文件内)从技术上逃避%,但在调用shell时这不起作用来自其他环境(如Node.js)的命令,通常不会跨平台工作。

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

%

插入的双引号会阻止// 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()) cmd.exe等标记识别为变量引用(%cd%不会被扩展)。

这样可行,因为当目标程序处理时,额外的双引号最终会从字符串中删除:

  • Windows "%"cd"%"(可能是通过C运行时)然后负责从合并后的字符串中删除额外的双引号。

  • 类Unix (类似POSIX的shell,如Bash):shell本身会在将双引号传递给目标程序之前将其删除。

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

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

通过完全避免shell来绕过问题

注意:以下构建于%,仅适用于调用外部可执行文件 (在OP& #39; s case:execFile[Sync]) - 相比之下,对于调用 shell builtins (内部命令)或Windows 批处理文件,您无法避免{{1}因此git.exe解释(在Windows上)。 [1]

如果您使用MC ND's answer 而不是exec[Sync],则 shell(Windows上为cmd.exe)将不会涉及,因此您无需担心转义execSync字符。或任何其他shell元字符,就此而言:

cmd.exe

请注意必须将单独作为数组的元素提供,而不使用嵌入式引号

[1]在Windows上,无法使用%直接调用脚本文件(例如,Python脚本),但您可以将解释器可执行文件(例如,require('child_process').execFileSync('git', [ 'log', '--format=%C(cyan)%cd%Creset %s', '--date=short', '-5' ], {stdio: 'inherit'}) )作为要执行的文件,以及脚本文件作为参数。在类似Unix的平台上,你可以直接调用脚本,只要它们有execFileSync并标记为可执行文件。
execFile[Sync] 可以用于调用Windows 批处理文件,但python总是插入参数,与execFile[Sync]一样。

答案 1 :(得分:3)

简而言之,你做不到或者至少没有一般的方法

注意我错了。 mklement0's answer为一般解决方案指明了方向。

当然,您可以使用^,而不是逃避百分号(您无法在命令行级别转义百分号),而是将百分号与变量名称分开,因此它不是检测为要扩展的变量。也就是说,对于您的情况,使用

就足够了
git log --format="%C(cyan)%cd^%Creset %s" --date=short -5

但正如您所指出的那样,插入符号不会使用插入符号并包含在输出字符串中。

因此,任何"转义" 字符都将包含在cmdbash的输出中,但可以针对此特定字符进行求解(或类似)使用

的情况
process.env.cd = '%cd%'
require('child_process').execSync(
    'git log --format="%C(cyan)%cd%Creset %s" --date=short -5'
    , {stdio: 'inherit'}
)

基本上,代码所做的是为cd环境变量分配一个伪值,将其更改为文字%cd%,因此,当cmd解析器执行变量扩展时,正确的值在命令行中结束。

但是,也许(我没有检查git代码如何/更改cd变量可能会干扰)。因此,对于这种情况,您可以使用

而不是更改cd变量的值
process.env['C(cyan)'] = '%C(cyan)%';

为什么呢?当cmd解析器处理命令行时,它将匹配格式字符串与现有变量的开头,执行扩展并在同一进程中使用%cd%变量中的起始百分号,它没有扩大。