从ROS1迁到ROS2,最劝退我的不是“一切皆节点”的理念,而是命令行全变了。rostopic变成了ros2 topic,rosrun变成了ros2 run,连编译工具都从catkin_make换成了colcon build。如果你刚装完ROS2,或者已经跑通了小海龟但每天还在“查命令”过日子,这篇文章就是给你准备的。这一篇是系列第三篇,前两篇把安装和基本概念理清了,这篇专门整理ROS2开发常用命令——不按man page的顺序抄,而是按每天实际干活的流程来梳理,每条命令都会说清楚为什么用、输出长什么样、什么地方最容易踩坑。命令本身不难,难的是当你面前有一个编译不过的包、一个收不到数据的节点、一个录完但回放不对的bag时,能不能准确想起该用哪条命令、哪个参数能救你。这篇就是干这个用的。
1. source、overlay与colcon build:开终端和编译前必知的底层逻辑
1.1 source的真相:为什么每次开终端都要先“激活”环境
很多新手第一次接触ROS2时都会遇到这个场景:明明按照教程装完了,关掉终端再打开,输入ros2却提示“command not found”。这时候教程会让你执行source /opt/ros/humble/setup.bash,但不解释为什么。其实ROS2的安装本质上就是往系统里放了一批库和可执行文件,同时定义了一堆环境变量。source这条命令做的事,就是把这些环境变量加载到当前终端进程里。
环境变量是进程级的,每个新终端都是一个新的shell进程,不会继承另一个终端里source过的内容,所以要重新source。你可以用echo $ROS_DISTRO来确认当前终端里有没有成功加载ROS2环境,如果输出是humble或jazzy这类发行版代号,说明环境已经激活。
我建议把这一行加进.bashrc,省得每次开终端都手动敲。但有个坑:如果你机器上装了多个版本的ROS2,比如同时装了humble和jazzy,千万不能把两个source都写进.bashrc,后source的会覆盖前面的,导致PATH和AMENT_PREFIX_PATH变成最后那个版本,你在终端里敲ros2,实际用的是另一个版本。这种情况我一般只在.bashrc里保留主用版本,需要切换时手动source另一个。
还有一个细节容易被忽略:不光是开新终端需要source,如果你用VS Code的终端、tmux新窗口、或者通过ssh登录,这些新shell同样要重新source。很多“编译能找到包但运行时找不到”的诡异问题,根源都是新终端里环境没加载。
1.2 overlay机制:多个工作区叠加,依赖包怎么找
ROS2的环境是支持叠加的,官方叫overlay。系统安装的ROS2在/opt/ros/humble,这是base环境。你自己创建工作区后,在~/ros2_ws里执行colcon build会生成install目录,source ~/ros2_ws/install/setup.bash之后,你本地编译的包就叠加在系统环境之上。
叠加不是替换,是扩展。比如系统环境里有一个包叫turtlesim,你本地工作区也有一个同名包,叠加之后系统那个会被“隐藏”,优先使用你本地编译的版本。这个机制对开发非常友好,你只需要编译自己改动的包,不改动系统里的其它包。
问题往往出在依赖查找顺序上。ROS2通过AMENT_PREFIX_PATH这个环境变量来记录所有已加载工作区的路径,顺序越靠前的越优先。如果你改了自定义消息包,但没有重新source本地工作区,其他包编译时会从系统的AMENT_PREFIX_PATH里找消息定义,自然找不到。我自己的习惯是:每次colcon build之后都会顺手source一遍install/setup.bash,有时候甚至是source ~/.bashrc,因为我在.bashrc里写好了source命令,这样能保证当前终端一定用的是最新的编译产物。
1.3 colcon build常用参数:别再用裸build了
colcon build是ROS2默认的编译工具,它的设计思路是每个包单独编译,按依赖顺序自动排序。最基本的使用就是在工作区根目录执行colcon build,但实际开发中我几乎不会直接用裸build,太浪费等待时间。
最常用的是--packages-select参数,只编译当前改动的包:colcon build --packages-select my_pkg。如果你的包依赖另一个本地包,可以加--packages-up-to my_pkg,它会把你指定包以及它依赖的本地包一并编译,又不会编译整个工作区。
--symlink-install这个参数强烈建议加上。它会把Python包、launch文件、配置文件以软链接方式安装到install目录,而不是拷贝。效果是你修改Python源码或launch文件后,不需要重新编译就能生效。对C++包无效,改了.cpp还是得重新build,但光是省掉Python和launch重编译的时间就值了。
还有两个容易被忽略的参数。--cmake-args用来给CMake传参,最典型的是指定编译模式:colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release,Release模式下运行效率更高,但编译更慢,调试阶段可以用默认的RelWithDebInfo。--event-handlers console_direct+则会把编译日志直接打到终端,而不是全部缓存到log目录。编译报错时,这个参数能让你第一时间看到是哪一行报错,不用再翻log文件。
编译时遇到“找不到依赖包”的报错,先别急着怀疑colcon。你要检查三件事:依赖包是否已经编译并source;是否通过apt安装了二进制版本;当前终端的环境变量是否包含了依赖包所在的路径。顺序排查,90%的问题出在第二个条件上——你编译的包依赖的某个第三方库没装,rosdep或者手动apt install一装,重新编译就过了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 用ros2 node和ros2 topic快速摸清系统里正在发生什么
2.1 node list和node info:节点关系一查便知
排查ROS2系统问题,我第一步永远是ros2 node list。它会打印当前系统中所有活跃节点。节点名前面自带命名空间,比如/ turtlesim这个节点,加上命名空间后可能显示为/ns/turtlesim。如果你的节点没出现在列表里,说明节点根本没启动起来,或者启动后崩溃退出了,这时候去查代码和日志,而不是继续查话题。
ros2 node info /节点名能看到这个节点的详细通信关系:Subscribers、Publishers、Service Servers、Service Clients、Action Servers、Action Clients、Parameters。这是排查“节点间为什么没通信”的第一利器。
我举个真实场景:你写了一个订阅/cmd_vel的机器人控制节点,发布端也在发数据,但机器人就是不动。用ros2 node info /my_controller一看,发现Subscribers列表是空的,说明订阅根本没建立成功。这时候要检查话题名是否一致、消息类型是否匹配、节点是否真的执行到了订阅代码。只用echo /cmd_vel看不到任何数据,永远查不出这个问题的根源。
2.2 topic echo/hz/bw:话题调试三板斧
ros2 topic echo应该是日常用得最多的命令,它把话题内容实时打印到终端。建议记住几个高频参数:--once表示只打印一次立即退出,适合确认数据结构;--field可以直接提取某个字段,比如ros2 topic echo /odom --field pose.pose.position.x,只输出x坐标,在做直线运动测试时非常有价值,不用盯着满屏的协方差矩阵看。
ros2 topic hz用来统计话题发布频率,单位是Hz。如果你怀疑某个传感器数据不稳定,比如激光雷达预期是10Hz,实测只有2Hz,说明节点内部有阻塞。ros2 topic bw查看话题占用带宽,在做多机通信、带宽优化时用得上。ros2 topic delay统计话题从发到收的延迟,适合验证实时性要求高的控制话题。
echo还有一个隐藏的坑:QoS不匹配。ROS2的话题通信默认是reliable策略,但很多传感器驱动(比如相机、雷达)为了降低延迟,会把QoS设成best_effort。如果你在接收端用默认的reliable去订阅,是订阅不到数据的,echo也收不到。这时候加上--qos-reliability best_effort参数就能看到了。这个坑我在调试时踩过不止一次,每次都浪费不少时间。
ROS1老用户迁移过来,最需要的就是一张命令对照表,我放这儿:
| 操作 | ROS1 | ROS2 |
|---|---|---|
| 查看节点列表 | rosnode list | ros2 node list |
| 查看话题列表 | rostopic list | ros2 topic list |
| 查看话题数据 | rostopic echo /topic | ros2 topic echo /topic |
| 手动发布话题 | rostopic pub -r 10 /topic std_msgs/String "data: 'hi'" | ros2 topic pub --rate 10 /topic std_msgs/msg/String "{data: 'hi'}" |
| 编译工作区 | catkin_make / catkin build | colcon build |
| 运行节点 | rosrun pkg node | ros2 run pkg node |
| 启动launch | roslaunch pkg file.launch | ros2 launch pkg file.launch.py |
| 调用服务 | rosservice call /srv std_srvs/srv/Empty | ros2 service call /srv std_srvs/srv/Empty "{}" |
| 录制bag | rosbag record -a | ros2 bag record -a |
| 回放bag | rosbag play bag | ros2 bag play bag |
| 设置参数 | rosparam set /node param value | ros2 param set /node param value |
注意一个细节:ROS2的消息类型名里多了msg字段,原来写std_msgs/String,现在要写std_msgs/msg/String;服务类型同理,要带上srv;动作类型带上action。这个改动初期特别容易忘,漏了msg会导致命令直接报错。
2.3 topic pub:不写代码也能往话题里灌数据
手动发布话题是验证系统功能的神器,不需要写任何代码。最典型的例子是测试机器人底盘:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.2}, angular: {z: 0.1}}" --rate 10
这条命令以10Hz的频率发布速度指令,x方向0.2m/s,z方向角速度0.1rad/s。如果先运行一个订阅/cmd_vel的节点或者直接在rviz2里看机器人模型,就能立刻看到效果。--rate参数指定发布频率,--once表示只发一次,适合测试单次触发型逻辑。
写消息内容时要留意YAML语法里的引号。花括号包裹的字段结构在bash里有时会被特殊处理,所以我习惯用单引号把整条消息包起来,字段内部的双引号用来包字符串值。字符串字段写成"data: 'hello'"这样的嵌套形式,跟ROS1里的写法基本一致。
还有个实用技巧:如果你怀疑自己的接收端代码处理不了某个字段,但不想反复改代码测试,可以用topic pub配合--rate手动灌数据,观察ros2 topic echo的输出验证接收端行为。配合--qos-reliability参数,还能模拟不同QoS下的通信效果,比自己写测试节点快得多。
2.4 daemon缓存问题:列表“卡死”时的第一解决思路
用ROS2一段时间后基本都会遇到这个现象:ros2 node list执行后卡住不动,或者系统里明明有节点,列表里就是显示不全。这时候最有效的处理手段是重启daemon。
ROS2有一个后台守护进程叫daemon,它缓存了系统的节点、话题、服务等graph信息。正常情况下它帮我们省掉了每次扫描DDS的时间,让ros2 node list这类命令返回很快。但缓存有时候会跟真实状态不一致,比如节点退出后daemon没及时更新,或者网络环境变化导致发现信息过期。
处理命令很简单:
ros2 daemon status
ros2 daemon stop
ros2 daemon start
stop之后不需要手动start,下次执行任何ros2命令时daemon会自动重新启动。注意一点:如果你修改了RMW实现(比如从Fast DDS换成Cyclone DDS),也要重启daemon,否则它可能还在用旧配置维护graph缓存,导致很多莫名奇妙的通信问题。
3. 服务、动作与参数:三种常用调试命令的完整用法
3.1 service call:调用接口验证业务逻辑
话题适合持续的数据流,服务则适合一次性的请求响应。排查问题时用ros2 service call手动调用一个服务,可以快速验证节点对外提供的功能是否正常。
先看服务列表:ros2 service list -t,-t会显示服务类型,这样你能知道这个服务接受的请求格式。如果只记得服务类型、想反查哪些节点提供了这类服务,用ros2 service find std_srvs/srv/Empty,会列出所有匹配的服务名。ros2 service type /服务名告诉你指定服务的类型。
实际调用示例,用turtlesim提供的小海龟生成服务:
ros2 service call /spawn turtlesim/srv/Spawn "{x: 2.0, y: 2.0, theta: 0.2, name: 'turtle2'}"
这条命令会在小海龟窗口的坐标(2.0, 2.0)处生成一只名为turtle2的新海龟。调用的返回结果会在终端里打印response结构,包括新生成的乌龟名。空参数的请求要写成:
ros2 service call /clear std_srvs/srv/Empty "{}"
这里给{}加引号是为了防止bash把它解析成代码块语法,实际发送的是一个空的请求结构。服务调试和话题调试同样要关注接口路径、类型、参数格式是否匹配,任何一项不对都会得到错误提示而不是直接执行。
3.2 action send_goal:长任务和实时反馈怎么玩
动作(action)可以理解为“带反馈的服务”,适合执行时间长的任务,比如导航到某个目标点、机械臂移动到某个位姿。ROS2命令行里对动作的支持也很完整。
ros2 action list -t查看所有动作列表及类型。ros2 action info /动作名查看这个动作的服务器和客户端状态。真正触发动作靠ros2 action send_goal,以turtlesim的海龟旋转动作为例:
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}" --feedback
这条命令让海龟旋转到1.57弧度,也就是约90度。加上--feedback参数,会持续输出动作执行的实时反馈,这跟service“请求后干等结果”的模式完全不同。动作命令返回的是目标接受状态、执行结果,适合验证长任务的整体流程是否通畅。
如果你要测试自己写的动作服务器节点,send_goal就是最直接的调试入口。先不带--feedback确认目标能被接受,再加--feedback验证中间反馈是否在持续输出,最后看终端的最终结果状态。三步走完,动作服务器的核心逻辑基本就验干净了。
3.3 param get/set/dump/load:运行时不重启改配置
ROS2参数系统是运行时可读写的,这给调试带来了极大便利。最常用的几组命令:
ros2 param list /节点名,列出该节点的所有参数。ros2 param get /节点名 参数名,读取参数当前值。ros2 param set /节点名 参数名 值,直接修改参数值。注意set的效果取决于节点是否实现了参数回调,有些节点只在初始化时读一次参数,set之后不会实时生效,这点要在自己写的代码里特别注意。
批量管理参数时用dump和load。ros2 param dump /节点名 --output-dir ~/params会把该节点所有参数导出成YAML文件;ros2 param load /节点名 ~/params/节点名.yaml一次性加载。调试多个节点时,把每个节点的参数dump下来,改完再load,比一个参数一个参数地set高效得多。
参数调试里最经典的坑是use_sim_time。ROS2的bag回放经常需要配合时间同步:在回放前先ros2 param set /your_node use_sim_time true,节点才会使用bag里的仿真时间,而不是系统墙钟时间。如果忘了设这个参数,节点会按真实时间处理,bag里录制的数据时间戳和当前时间完全对不上,导航和SLAM算法会表现得极其诡异。这个坑我印象太深了。
4. 新建功能包与消息接口:从pkg create到interface show
4.1 pkg create的选型参数:ament_cmake还是ament_python
在ROS2里新建功能包,不推荐手动创建目录和CMakeLists.txt,直接用ros2 pkg create自动生成骨架。
最简单的C++包:
ros2 pkg create my_pkg --build-type ament_cmake --dependencies rclcpp std_msgs
Python包:
ros2 pkg create my_pkg --build-type ament_python --dependencies rclpy std_msgs
--build-type二选一,决定包的类型;--dependencies后面跟的是这个包要依赖的其它包,创建时会自动写入package.xml和CMakeLists.txt。C++包还会在src目录下生成一个简单的hello world源文件,Python包则生成setup.py和对应的包目录结构。
选型上我的建议是:如果你是写算法、工具类或者要发布给团队复用的包,用ament_cmake,C++的性能优势和类型安全在机器人领域不可替代;如果你是快速验证想法、写数据可视化脚本或者非性能敏感的逻辑,用ament_python,开发效率高得多。一个工作区里两种包可以共存,互不影响。
4.2 package.xml依赖和ros2 pkg executables:可执行文件找不到了怎么办
package.xml是描述包元数据和依赖关系的文件。build_type、dependencies字段都在这里。编译时colcon会读取package.xml,通过依赖关系构建编译顺序。运行时节点需要加载的动态库,也要靠package.xml声明的依赖来保证被安装到正确位置。所以不管是手动加依赖还是用--dependencies,最终都要确保package.xml里的depend字段是完整的。
另一个经常会用到的命令是ros2 pkg executables。当你用ros2 run my_pkg my_node时报错“找不到可执行文件”,先用这个命令看看包到底提供了哪些可执行目标:
ros2 pkg executables my_pkg
它会列出包内所有可执行文件的名称列表。如果列表里没有你期望的node,说明
