C#运行.bat时SSH连接Linux Shell后后续命令无法执行问题
bat脚本的执行逻辑是逐行阻塞执行:只有当前行启动的进程完全退出后,才会解析执行下一行命令。
你当前脚本里的ssh命令启动后会进入交互式远程会话,只要SSH连接不主动断开,本地cmd进程会一直卡在这一行等待,后面的timeout、echo命令根本不会被本地系统加载执行。同时原生SSH交互式会话不会自动把本地脚本后续的行转发到远端Linux Shell,你手动执行时能正常操作,是因为你手动在SSH会话窗口里输入指令,脚本本身没有自动给SSH会话喂输入的能力。
额外注意:你写的timeout /T 5 /NOBREAK是Windows cmd专属命令,就算被转发到远端Linux VM也无法被识别执行,Linux下对应的延时命令是sleep 5。
按实现稳定性、自动化适配度从高到低排序:
1. C#直接用SSH库实现逻辑(最推荐)
既然最终流程由C#承载,不需要绕一层bat调用系统SSH客户端,直接用成熟的SSH库完成连接、命令执行全流程,可控性最高,没有脚本解析、进程阻塞的兼容问题。
推荐使用开源轻量库SSH.NET,直接通过NuGet安装即可,不依赖本地环境配置,支持私钥认证、命令执行、文件传输、交互式Shell等全场景能力。
极简实现示例:
using Renci.SshNet; // 加载私钥、初始化连接 var privateKey = new PrivateKeyFile("BatchStagingVM_key.pem"); var authMethod = new PrivateKeyAuthenticationMethod("azureuser", privateKey); var connInfo = new ConnectionInfo("xx.xxx.xx.xx", "azureuser", authMethod); using var sshClient = new SshClient(connInfo); sshClient.Connect(); // 执行远端命令,支持多命令拼接 var command = sshClient.CreateCommand("sleep 5 && echo blah"); // 可直接获取命令执行的标准输出、错误输出、返回码 var output = command.Execute(); Console.WriteLine(output); sshClient.Disconnect();
如果需要实现持续交互的Shell场景(比如运行长期驻留的程序、动态下发指令),可以用库提供的CreateShell方法直接读写Shell流,自主控制输入输出时机。
2. 用SSH非交互模式直接传递远端命令
如果一定要保留bat脚本实现,不需要保留交互式会话,直接把要在远端执行的所有命令作为SSH参数传入,SSH连接建立后会自动在远端执行完所有命令,执行完成后自动断开连接,不需要加本地延时逻辑。
修改后的bat脚本示例:
@echo off :: 所有Linux侧命令直接拼在SSH连接参数后,用引号包裹 ssh -i BatchStagingVM_key.pem azureuser@xx.xxx.xx.xx "sleep 5 && echo blah"
如果要执行多条远端命令,直接在引号内用;或者&&拼接即可,比如"cd /home/azureuser; ls -al; sleep 5; echo blah"。
3. 输入重定向批量传递命令
如果要执行的远端命令较多,直接拼在参数里可读性差,可以把所有远端命令单独存到文本文件里,通过输入重定向把文件内容喂给SSH会话执行。
步骤如下:
- 新建
remote_commands.txt,写入所有要在Linux执行的命令,最后加exit主动断开连接:
sleep 5 echo blah exit
- 修改bat里的SSH命令为:
ssh -i BatchStagingVM_key.pem azureuser@xx.xxx.xx.xx < remote_commands.txt
注意:这种方式不支持需要交互响应的场景(比如远端命令要求输入密码、确认操作),纯批量执行无交互命令时可以使用。
4. 用plink替代原生SSH客户端
如果需要更稳定的Windows下命令行SSH自动化能力,可以使用Putty套件自带的plink工具,专门针对自动化场景做了适配,支持直接指定命令脚本文件、批量传参,避免原生SSH客户端在bat里的交互兼容问题。
示例用法:
:: -m 参数指定存放远端命令的文本文件 plink -i BatchStagingVM_key.ppk azureuser@xx.xxx.xx.xx -m remote_commands.txt
注意plink默认使用ppk格式私钥,需要提前把现有的pem格式私钥用PuttyGen转换为对应格式。
- 不要在SSH交互式命令行后接本地Windows命令,只要SSH进程不退出,后续命令永远不会被本地执行
- 区分本地Windows命令和远端Linux命令,不要把
timeout这类Windows专属指令写到远端执行逻辑里 - 自动化场景优先选择非交互命令执行模式,稳定性远高于交互式会话,不会因为终端缓冲、转义问题出现异常
内容的提问来源于stack exchange,提问作者TheChessLobster

