Qt 调 ROS2 为什么总是 command not found?

用 Qt 给 ROS2 做命令大全:为什么当年死活跑不通?

一次被 command not found 卡死的项目,一次迟来的复盘。


前言

几年前,我想做一个很简单的小工具。

左边是一列 ROS2 常用命令:

  • ros2 topic list
  • ros2 node list
  • ros2 service list
  • ros2 param list

右边一个「执行」按钮。

点一下。

自动打开一个终端。

自动执行命令。

对于新手来说,不需要记命令,不需要查文档,只需要点按钮。

我甚至连界面都已经用 Qt 做好了。

结果项目死在了最后一步。

终端弹出来了。

命令也输入进去了。

但所有 ROS2 命令全部报错:

bash 复制代码
ros2: command not found

当时折腾了很久。

最后项目被我扔进了硬盘。

直到最近重新翻到当年的代码,我才发现,问题根本不在 Qt。

而在 Linux Shell。


为什么手动打开终端没问题?

ROS2 用户几乎都会在:

bash 复制代码
~/.bashrc

里面写:

bash 复制代码
source /opt/ros/humble/setup.bash
source ~/ros2_ws/install/setup.bash

所以每次打开终端:

bash 复制代码
Ctrl + Alt + T

ROS2 都可以直接使用:

bash 复制代码
ros2 topic list

这让很多人误以为:

ROS2 命令本来就存在于系统中。

实际上并不是。

真正发生的是:

text 复制代码
打开终端
        ↓
bash 启动
        ↓
读取 ~/.bashrc
        ↓
source ROS2
        ↓
环境变量加载
        ↓
ros2 可执行

而 Qt 启动终端时,这个流程发生了变化。


Qt 打开的终端为什么不行?

Qt 通常通过:

cpp 复制代码
QProcess

或者:

cpp 复制代码
system()

启动外部终端。

例如:

cpp 复制代码
gnome-terminal -- bash -c "ros2 topic list"

这里的:

bash 复制代码
bash -c

属于非交互 shell。

而非交互 shell:

  • 不读取 ~/.bashrc
  • 不读取 ROS 环境
  • 不加载 PATH

于是:

bash 复制代码
ros2

根本不存在。

最终就出现:

bash 复制代码
command not found

这也是为什么:

  • 你自己打开终端没问题;
  • Qt 打开的终端全部报错。

因为它们根本不是同一种 shell。


第一种解决方案:显式 source

最直接的方法。

执行命令之前先手动加载环境。

cpp 复制代码
QString cmd =
    "source /opt/ros/humble/setup.bash && "
    "source ~/ros2_ws/install/setup.bash && "
    "ros2 topic list";

然后:

cpp 复制代码
gnome-terminal -- bash -c "..."

最终执行:

bash 复制代码
source /opt/ros/humble/setup.bash &&
source ~/ros2_ws/install/setup.bash &&
ros2 topic list

这是最稳定的方案。

无论用户有没有配置 bashrc。

都能运行。


第二种方案:交互式 shell

bash:

bash 复制代码
-i

表示:

text 复制代码
interactive shell

于是:

cpp 复制代码
bash -ic "ros2 topic list"

bash 会主动读取:

bash 复制代码
~/.bashrc

如果用户提前配置:

bash 复制代码
source /opt/ros/humble/setup.bash

那么命令就能执行。

但缺点也很明显。

换台电脑可能直接失效。


第三种方案:登录 shell

还有:

bash 复制代码
-l

即:

bash 复制代码
login shell

它会读取:

bash 复制代码
~/.profile
~/.bash_profile

效果类似。

不过 Linux 用户配置方式差异很大。

实际项目里并不推荐依赖。


后来我发现:别人根本不这么干

后来我去翻了一些 Qt + ROS2 项目。

结果发现一个很有意思的现象。

它们几乎都不会:

text 复制代码
Qt
    ↓
打开新终端
        ↓
执行 ros2

而是:

text 复制代码
source 环境
        ↓
ros2 run xxx
        ↓
启动 Qt
            ↓
QProcess 调 ros2

也就是说:

Qt 本身就是 ROS2 节点。

这样:

  • Qt 有环境;
  • 子进程继承环境;
  • 根本不会出现 source 问题。

问题从根源上消失。


于是出现了两条路线

路线一:做成 ROS2 包

启动:

bash 复制代码
ros2 run ros2_command_gui app

优点:

  • 环境永远正确;
  • 部署简单;
  • 不需要处理 source;
  • 可以内嵌终端。

这是现在主流 GUI 工具的做法。


路线二:继续打开外部终端

仍然:

text 复制代码
点击按钮
    ↓
打开终端
    ↓
执行命令

但在执行前:

bash 复制代码
source ROS2

这样就能保证环境正确。

这种模式其实目前很少有人做。

反而可能是一个空白方向。


这几年最大的收获

回头看。

当年让我放弃整个项目的问题。

其实只有一句话:

非交互 shell 不会读取 bashrc。

Qt 没问题。

ROS2 没问题。

QProcess 也没问题。

真正的问题,是当时对 Linux shell 的理解还不够。

很多时候。

一个看似卡了几年的坑。

最终可能只是一行:

bash 复制代码
source setup.bash

而现在,这个项目终于又可以重新开始了。