这本是个古老的话题,在 Windows (>=) 10 和 Windows Terminal 已成主流的今天,在 PowerShell Core 愈发好用的今天,又为何食古不化、老调重弹呢?
因为 Windows Terminal 每次更新都强杀进程啊!因为 PowerShell (>=) 6 每次更新都得关掉所有实例进程啊!
笔者在探索一种在 Windows 上安稳地运行 CLI 程序的方案。
WT 的替代方案
WT 作为 ConHost UI 的替代品,它的替代品目前只有:
根据 微软员工的说法 ,这俩是一个东西的两种发行方式。至于这段历史和爱恨情仇,即微软花大力气拆分、重构 ConHost 并提供更现代的 WT,可以看 这篇微软博客 。
笔者使用的是 Win11 22H2。默认终端设为 WT,那么直接运行 cmd.exe pwsh.exe 等,会启动 WT 进程并把 CLI 进程挂靠其上。
# Win+R
"C:\My Root\a.exe" -c "C:\My Root\a.json"
绕开默认终端的办法也简单,运行 conhost.exe <exe> <args>
就行了。
如果参数包含空格,使用 "
包裹即可。例如:
# Win+R
conhost "C:\My Root\a.exe" -c "C:\My Root\a.json"
向 powershell 传递参数
呼应开头,Windows PowerShell (powershell.exe) 是一个稳定的 shell。它相比 cmd 速度略慢(你启动看看就懂了),但拿来做事情也是方便的,毕竟它提供的功能现代多了。
用单引号而非双引号,巧妙地绕开问题:
# Win+R
powershell.exe -NoExit -Command "& 'C:\My Root\a.exe' -c 'C:\My Root\a.json'"
# powershell.exe 拿到的参数列表是(不含头尾双引号)
# 0 "powershell.exe"
# 1 "-NoExit"
# 2 "-Command"
# 3 "'C:\My Root\a.exe' -c 'C:\My Root\a.json'"
然而,Win+R 像 cmd 一样处理字符串和 "
:
字符串以空格隔开,除非正在匹配 "
。
匹配完并不立即截断。遇到空格才截断。
匹配完紧跟着的一个 "
得到保留。
遇到行尾,匹配没成对,也视为合法结束。
""
是 空
。
" "
是 一个空格
。
"""
是 "
已闭合。
""""""
是 ""
已闭合。
""" """
是 "
, "
已闭合。
"""" ""
是 " "
已闭合。
""""
是 "
未闭合。
"""""
是 "
已闭合。
a" "b
是 a b
。
"a"" b"
是 a"
b
。
""a" b"
是 a b
。
" a""b [ "" ] """"""
是 a"b
, [
, 空
, ]
, ""
小提示:打开 vscode,设置为 Batch 格式的语法高亮,写两段,观察高亮的字符串范围。
参见以下例子:
# Win+R
powershell.exe -NoExit -Command ""& 'C:\My Root\a.exe' -c 'C:\My Root\a.json'""
# powershell.exe 拿到的参数列表是(不含头尾双引号)
# 0 powershell.exe
# 1 "-NoExit"
# 2 "-Command"
# 3 "&"
# 4 "'C:\My"
# 5 "Root\a.exe'"
# 6 "-c"
# 7 "'C:\My"
# 8 "Root\a.json'"
# powershell 将 -Command 后的各参数合并执行
# & 'C:\My Root\a.exe' -c 'C:\My Root\a.json'
# Win+R
powershell.exe -NoExit -Command "& ""C:\My Root\a.exe""" -c """C:\My Root\a.json"""
# powershell.exe 拿到的参数列表是(不含头尾双引号)
0 "powershell.exe"
1 "-NoExit"
2 "-Command"
3 "& "C:\My"
4 "Root\a.exe""
5 "-c"
6 ""C:\My"
7 "Root\a.json""
# powershell 将 -Command 后的各参数合并执行
# & "C:\My Root\a.exe" -c "C:\My Root\a.json"
# Win+R
powershell.exe -NoExit -Command "& """C:\My Root\a.exe""" -c """C:\My Root\a.json""""
# powershell.exe 拿到的参数列表是(不含头尾双引号)
# 0 "powershell.exe"
# 1 "-NoExit"
# 2 "-Command"
# 3 "& "C:\My Root\a.exe" -c "C:\My Root\a.json""
# powershell 实际执行
# & "C:\My Root\a.exe" -c "C:\My Root\a.json"
不使用 Win+R,而是在 pwsh 脚本里的话。使用 ``"
转义,看起来干净、简单:
# pwsh
$exe = "C:\My Root\a.exe"
$ini = "C:\My Root\a.json"
& conhost.exe powershell.exe -NoExit -Command "& `"$exe`" -c `"$ini`""
向 cmd 传递参数
既然提了 PowerShell,那么传统的 Command Prompt 也看看罢。
当场失败:
cmd.exe /A /S /K "C:\My Root\a.exe" -c "C:\My Root\a.json"
'C:\My' is not recognized as an internal or external command,
operable program or batch file.
似乎是 exe 路径被截断了。
解释一下这里的参数:
/A
使 cmd 内部命令的输出是 ANSI(相对于 Windows Unicode)
/S
禁用一种特殊处理模式
/K
运行后续命令、参数,结束后不关闭窗口
突然想到神奇的 @
指令,果然可以:
cmd.exe /A /S /K @ "C:\My Root\a.exe" -c "C:\My Root\a.json"
问题到这就解决了,这么写就行了。
/S
禁用针对一个字符串的特殊处理
/C
/K
后边跟着的第一个字符串,一般是程序路径。如果它被双引号包裹,且只有这个字符串被双引号包裹,那么会特殊处理:
cmd /A /K "C:\My Root\a.exe" -h
conhost cmd /A /K "C:\My Root\a.exe" -h
禁用特殊处理,就报错了:
cmd.exe /A /S /K "C:\My Root\a.exe" -h
'C:\My' is not recognized as an internal or external command,
operable program or batch file.
不满足条件,也报错:
cmd.exe /A /K "C:\My Root\a.exe" -c "C:\My Root\a.json"
'C:\My' is not recognized as an internal or external command,
operable program or batch file.
另辟蹊径:单字符转义 ^[c]
查阅文档,得知 cmd 提供了单字符转义,即 ^[c]
表示 c
。遂尝试。但是,a.exe 被执行了,传递给它的命令行参数列表却出了问题,在不该切分的地方切分了。
# Win+R
cmd.exe /A /K C:\My^ Root\a.exe -c "C:\My Root\a.json"
# a.exe 拿到的参数列表是(不含头尾双引号)
# 0 "C:\My"
# 1 "Root\a.exe"
# 2 "-c"
# 3 "C:\My Root\a.json"
error: Found argument 'Root\a.exe' which wasn't expected, or isn't valid in this context
这应该是 cmd 的 bug。无可奈何 Windows 闭源,我们无法修复它。遂放弃这条路。
cmd ...
和 conhost cmd ...
行为不同:
# 正确解析?
cmd /A /S /K ""C:\My Root\a.exe" -c "C:\My Root\a.json""
# 截断错误
conhost cmd /A /S /K ""C:\My Root\a.exe" -c "C:\My Root\a.json""
"C:\My"
"Root\a.exe -c C:\My"
"Root\a.json"
cmd /A /S /K """"C:\My Root\a.exe"""" -c """"C:\My Root\a.json"""
# cmd.exe 拿到的参数列表是(不含头尾双引号)
# 0 "cmd.exe"
# 1 "/A"
# 2 "/S"
# 3 "/K"
# 4 ""C:\My"
# 5 "Root\a.exe""
# 6 "-c"
# 7 ""C:\My"
# 8 "Root\a.json""
conhost cmd /A /S /K """"C:\My Root\a.exe"""" -c """"C:\My Root\a.json"""
# cmd.exe 拿到的参数列表是(不含头尾双引号)
# 0 "cmd.exe"
# 1 "/A"
# 2 "/S"
# 3 "/K"
# 4 ""C:\My Root\a.exe""
# 5 "-c"
# 6 ""C:\My Root\a.json""
'\"C:\My Root\a.exe\"" -c "\"C:\My Root\a.json\"' is not recognized as an internal or external command,
operable program or batch file.
别折腾了,这么写就行:
conhost cmd /A /S /K @ "C:\My Root\a.exe" -c "C:\My Root\a.json"
字母数字或汉字
软件重构工程师 @未命名