不肯轻易退出的终端环境
我通常都使用 Windows Terminal (wt) 管理不同的终端环境,比如 MSYS2 的环境。
但 wt 相比 MSYS2 打包的 mintty 有一个问题:终端里最后一个命令退出代码非零的情况,按下 <kbd>Ctrl+d</kbd> 可能会无法自动关闭标签页,而是显示
注销
终止批处理操作吗(Y/N)?
并等待用户输入。更恼人的是,这个输入必须以 y 或者 n 开头才能关闭标签页,所以不能留空,也不能输入任意字符。
当前配置
查看一下终端环境的 profiles,我的配置如下
{
"closeOnExit": "always",
"commandline": "D:\\msys64\\msys2_shell.cmd -defterm -here -no-start -clang64 -shell bash",
"guid": "{some-random-guid-value}",
"hidden": false,
"icon": "D:\\msys64\\clang64.ico",
"name": "MSYS2: clang64",
"startingDirectory": "D:\\msys64\\home\\username"
}可以看出,已经设置了总是在退出时关闭标签页,那么不能关闭的原因就应该是还没真正退出。我们的 commandline 是一个 .cmd 批处理脚本,结合前面的询问信息,确定就是在退出这个批处理时,cmd.exe 想要我们的答复。
思考了一下,我们不通过这个脚本,直接把脚本实质运行的命令放在这里,是不是就不用等待这个退出确认了?修改一下试试看吧。
分析命令行作用
defterm
这是我的命令行第一个参数,看看 msys2_shell.cmd,它到底做了什么?
if "x%~1" == "x-defterm" shift& set /a msys2_shiftCounter+=1& set MSYSCON=defterm& goto :checkparams它将移除已解析的当前参数,设置变量 MSYSCON 为 defterm 然后返回参数检查的开头。这里的实质作用就是设置了变量,看看这个变量会起到什么作用吧
if "x%MSYSCON%" == "xdefterm" goto startsh如果变量是 defterm 直接去 startsh 标签,再看看 startsh 的定义
:startsh
set MSYSCON=
if not defined MSYS2_NOSTART (
start "%CONTITLE%" "%WD%\%LOGINSHELL%" -l !SHELL_ARGS!
) else (
"%WD%\%LOGINSHELL%" -l !SHELL_ARGS!
)
exit /b %ERRORLEVEL%清除了这个变量,然后根据 MSYS2_NOSTART 的定义执行不同的命令;不难看出,这里应该就是真正启动终端环境的地方了。那么,MSYS2_NOSTART 是如何定义的呢?我们暂且按下不表。
here
这是第二个参数,看看它是怎么解析的吧
if "x%~1" == "x-here" shift& set /a msys2_shiftCounter+=1& set CHERE_INVOKING=enabled_from_arguments& goto :checkparams仅仅是设置了 CHERE_INVOKING 变量为 enabled_from_arguments,之后的脚本也没有再使用这个变量,可以猜测,它的值会由最终执行的命令从环境变量中获取,并影响其初始目录。
no-start
if "x%~1" == "x-no-start" shift& set /a msys2_shiftCounter+=1& set MSYS2_NOSTART=yes& goto :checkparams第三个参数则设置了 MSYS2_NOSTART 变量,我们回到刚才 startsh 的分析,应该是执行 else 的操作 "%WD%\%LOGINSHELL%" -l !SHELL_ARGS!。这里有三个不同的变量,先看看 WD
set "WD=%__CD__%"
if NOT EXIST "%WD%msys-2.0.dll" set "WD=%~dp0usr\bin\"如果当前目录有 msys-2.0.dll,WD 就是当前目录,否则设置为该脚本所在目录的子目录 usr\bin\。行吧,对于我的情况,其实可以写死为 D:\msys64\usr\bin\ 了。其他两个变量再一次按下不表。
clang64
第四个参数代表了终端环境,MSYS2 有几个不同的环境,这里使用了 Clang x64。
if "x%~1" == "x-clang64" shift& set /a msys2_shiftCounter+=1& set MSYSTEM=CLANG64& goto :checkparams它设置了 MSYSTEM 变量为 CLANG64,这个变量会传递给执行的命令,并影响最终启动的环境。除了这一点,其实这个变量在批处理脚本中还影响了 CONTITLE 和 CONICON 的设置,不过前文 startsh 的分析中,我们得知,它们俩没有被真正使用。
-shell bash
最后一个参数设置了启动的 shell 为 bash,它就是 startsh 里提到的 LOGINSHELL。
还有什么
前面我们还剩下一个变量 SHELL_ARGS 没分析,来看看代码
set msys2_full_cmd=%*
for /f "tokens=%msys2_shiftCounter%,* delims=,;= " %%i in ("!msys2_full_cmd!") do set SHELL_ARGS=%%j把所有命令行参数以逗号、分号、等号、制表符、空格为分隔符拆分,将前文未解析的部分通通塞给 SHELL_ARGS,这将作为 bash 的参数。不过我这里的情况,这个变量为空。
组合为一条命令
将前文分析出的内容结合为一条命令,应该是什么呢?首先,我们希望 bash 退出后可以关闭终端,然后,我们还有环境变量需要传递给 bash,因此最终的配置如下
"commandline": "cmd.exe /c \"set CHERE_INVOKING=enabled_from_arguments && set MSYSTEM=CLANG64 && D:\\msys64\\usr\\bin\\bash.exe -l\""它使用了 /c 让 cmd 执行之后的命令,结束后退出;在其中设置了我们所需的环境变量,最后启动 bash 登录。