1,974

这本是个古老的话题,在 Windows (>=) 10 和 Windows Terminal 已成主流的今天,在 PowerShell Core 愈发好用的今天,又为何食古不化、老调重弹呢?

因为 Windows Terminal 每次更新都强杀进程啊!因为 PowerShell (>=) 6 每次更新都得关掉所有实例进程啊!

笔者在探索一种在 Windows 上安稳地运行 CLI 程序的方案。

WT 的替代方案

WT 作为 ConHost UI 的替代品,它的替代品目前只有:

  • ConHost.exe
  • OpenConsole.exe
  • 根据 微软员工的说法 ,这俩是一个东西的两种发行方式。至于这段历史和爱恨情仇,即微软花大力气拆分、重构 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" "ba 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"
        字母数字或汉字
            软件重构工程师 @未命名
          
    粉丝