非交互式SSH执行ROS2 Launch文件失败问题求助
问题:Golang SSH远程执行ROS2 Launch命令无节点启动
我用Golang实现了SSH连接机器人的程序,ros2 topic list、ros2 topic info等命令都能正常执行并返回结果,但执行ROS2 Launch文件时没有输出,机器人本地用ros2 node list也检测不到启动的节点。
我的Golang SSH执行代码如下:
func (serverMsg *BaseWSServerMsg) executeArbitrarySSH(payload interface{}, c *websocket.Conn) { type arbSSH struct { Data string `json:"data,omitempty"` Port int `json:"port,omitempty"` Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` Hostname string `json:"hostname,omitempty"` Type string `json:"type,omitempty"` } marshalled, err := json.Marshal(payload) if err != nil { serverMsg.CreateAsError(http.StatusBadRequest, err.Error()) return } var load arbSSH err = json.Unmarshal(marshalled, &load) if err != nil { serverMsg.CreateAsError(http.StatusBadRequest, fmt.Sprintf(err.Error())) return } config := &ssh.ClientConfig{ User: load.Username, Auth: []ssh.AuthMethod{ ssh.Password(load.Password), }, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } client, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", load.Hostname, load.Port), config) if err != nil { serverMsg.CreateAsError(http.StatusInternalServerError, err.Error()) return } session, err := client.NewSession() if err != nil { serverMsg.CreateAsError(http.StatusInternalServerError, err.Error()) return } if load.Type == "output" { fmt.Println("Recieved Output command") defer client.Close() defer session.Close() stdout, err := session.Output(load.Data) if err != nil { serverMsg.CreateAsError(http.StatusInternalServerError, err.Error()) return } serverMsg.Payload = struct { Data string `json:"data,omitempty"` }{string(stdout)} } }
由于每个命令都会创建新终端,我会在执行Launch命令前先source环境变量,示例命令:
export ROS_DOMAIN_ID=30 > /dev/null && source /opt/ros/humble/setup.bash > /dev/null && source ~/example_ws/install/local_setup.bash > /dev/null && ros2 launch example example.launch.py
这个命令在交互式SSH终端中完全正常,能启动节点并输出内容,但通过Golang执行就没效果。试过加&后台运行、用nohup都没用。
另外测试发现,直接用非交互式SSH执行上述命令,终端能显示ROS_INFO输出,但机器人本地ros2 node list和ros2 topic list还是检测不到节点,ROS_DOMAIN_ID是一致的。
原因分析
- 非交互式shell环境变量缺失:非交互式SSH会话不会自动加载
~/.bashrc或~/.profile,即使手动source了setup.bash,ROS2节点发现所需的隐性环境变量(如CYCLONEDDS_URI、ROS_LOCALHOST_ONLY)可能未正确设置。 - SSH会话结束导致进程终止:Launch命令是长期运行的进程,SSH会话关闭时会给子进程发SIGHUP信号,即使加了
&,进程仍会被终止。 - Golang
session.Output()的阻塞特性:Output()会等待命令执行完成才返回,而Launch进程不会主动退出,导致会话一直阻塞,最终进程被强制终止。
解决方法
1. 完整加载环境变量
用bash -i启动交互式shell,自动加载~/.bashrc中的配置,确保所有ROS2环境变量生效:
bash -i -c 'export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && ros2 launch example example.launch.py'
2. 让进程脱离SSH会话控制
用nohup+disown让进程后台运行并脱离会话组,避免被SIGHUP信号终止:
bash -i -c 'export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && nohup ros2 launch example example.launch.py > ~/launch_log.out 2>&1 & disown'
也可以用screen托管进程(需机器人已安装screen):
bash -i -c 'screen -dmS ros_launch bash -c "export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && ros2 launch example example.launch.py"'
3. 修改Golang代码适配长期运行命令
原代码用session.Output()会阻塞到进程结束,改用session.Run()执行后台命令,无需等待进程退出:
if load.Type == "output" { fmt.Println("Recieved Output command") defer client.Close() defer session.Close() // 替换为带交互式shell和后台启动的命令 cmd := fmt.Sprintf("bash -i -c 'export ROS_DOMAIN_ID=30 && source /opt/ros/humble/setup.bash && source ~/example_ws/install/local_setup.bash && nohup ros2 launch example example.launch.py > ~/launch_log.out 2>&1 & disown'") err := session.Run(cmd) if err != nil { serverMsg.CreateAsError(http.StatusInternalServerError, err.Error()) return } serverMsg.Payload = struct { Data string `json:"data,omitempty"` }{"Launch命令已执行,可查看~/launch_log.out获取日志"} }
验证步骤
- 在机器人上执行
ps aux | grep ros2,确认launch进程是否在运行 - 查看
~/launch_log.out文件,检查ROS2 Launch的输出是否正常 - 再次执行
ros2 node list,确认节点已被发现
内容的提问来源于stack exchange,提问作者user25009967
相关产品推荐
相关产品推荐

