grep命令搜索文本

grep可以设置搜索结果的颜色,查看环境变量发现已经做了设置:alias grep='grep --color=auto',最好不要再修改

跳过二进制文件

执行grep命令时,出现匹配到二进制文件,说明grep 的字符串在一个不开源的二进制文件源码中有用到,一般可以跳过,在命令最后加--binary-files=without-match,最好加入上面的alias里面

搜索以</set为行首的结果,跳过二进制文件:

1
grep -r  '^</set'  .  -n  --binary-files=without-match

搜索包含完整单词setup的结果,跳过二进制文件:

1
grep -r  '\<setup\>'  . -n  --binary-files=without-match

这样不会显示包含setups的结果

从结果中排除某字符串

grep用于从检索结果中获取含有某字符串的结果,但是如果想要的结果是排除某字符串的,可以使用grep -v string。最典型的应用就是:ps aux | grep process | grep -v grep,用于查看进程process,去掉grep本身的进程

获得apriltag_ros_continuous进程的个数
ps -ef | grep apriltag_ros_continuous | grep -v grep | wc -l

grep命令搜索多个字符串

1
grep 'fatal\|error\|critical'  test.log

需要注意的是,如果要搜索的字符串包含空格,需要用双引号将其括起来

正则表达式

常用正则表达式的格式符:

1
2
3
4
5
6
7
8
9
^: 行首锚定
$: 行尾锚定
^PATTERN$: 用于PATTERN来匹配整行

\<: 词首锚定
\>: 词尾锚定
\<PATTERN\>: 匹配完整单词
. (小数点):代表『一定有一个任意字节』的意思;
* (星号):代表『重复前一个字符, 0 到无穷多次』的意思,为组合形态

常用的是词首和词尾锚定,

1
2
grep -n 'g..d' file.txt    # g 与 d 之间一定要存在两个字节
grep -n '\<good\>' file.txt # 找出完整的good

指定文件类型

--include可以指定搜索的文件类型,例如:

1
grep -r "网络" . -n --include "*.sh"

搜索时只搜索sh文件中的文本

排除文件类型

--exclude可以排除文件类型,例如:

1
grep -r "网络" . -n --exclude "*.bak"

搜索时排除bak文件类型

另一种方式

查找内容含有abc的文件: grep "abc" *

搜索 ——

-符号比较特殊,这样是错误的,不会有结果

1
2
grep "-----" test.cc    # 无论加不加 -rn
grep -e "-----" .

正确的

1
2
grep -e "-----" test.cc
grep -- "-----" *.cc

参考:
grep命令的使用 && 正则表达式


Ubuntu系统装机和初始化

安装

必须用U盘启动方式进入系统,否则会报错:
Alt text
步骤:

  1. 使用PowerISO和Ubuntu的ISO压缩包制作启动U盘
  2. 先选择”try me without install”模式,进入系统后看看有什么问题,比如无线网能不能连接,没什么问题后,退出重启,按流程装好Ubuntu系统
  3. 插上U盘重新启动Ubuntu,选择”try me without install”模式。如果没有从U盘启动,按住F2进入BIOS,到BOOT标签,修改启动的优先顺序为U盘启动为第一优先
  4. 从”try me”模式进入系统后,打开GParted
  5. umount硬盘,然后按提示进行Resize partition
  6. 每次分盘要等待一段时间,完成后拔出U盘重启电脑
  7. 更改各磁盘的 label
    输入 sudo fdisk -l,然后按照sudo e2label /dev/sda1 "diskname"的格式修改

安装 Qt

安装Qt必须根据系统是32位还是64位,从官网下载对应版本的Qt安装包,然后./qt-***,在安装完成后缺少编译器和调试器。从软件安装程序里安装CMake,然后安装gcc和GDB:

1
sudo apt-get install gcc && gdb

  ,完成后运行程序可能报错:cannot find -lGL collect2:error:ld returned 1 exit status 因为缺少opengl库
在联网状态下:sudo apt-get libgl1-mesa-dev

禁止窗口 System program problem detected 弹出

sudo gedit /etc/default/apport,将参数1改为0

安装字体

从Windows复制或从网上下载后,放到 /usr/share/fonts即可

安装中文输入法

参考这个教程,要删除ibus框架,其实就是把系统设置为中文,否则无法切换。都装完后重启,最好不要把几个主要目录改成中文名称,到fcitx的设置里添加搜狗输入法。一般就成功了。

trash-cli 不直接删除文件

这个工具是把文件放到了回收站,不是永久删除

sudo apt install -y trash-cli

  • trash-put 把文件或目录移动到回收站
  • trash-empty 清空回收站
  • trash-list 列出回收站文件
  • trash-restore 恢复回收站文件
  • trash-rm 删除回收站文件

安装oh-my-zsh

  1. 运行cat /ect/shells可知目前安装的shell,一般默认是bash
  2. 运行sudo apt-get install -y zsh安装zsh,再运行上面的命令发现多了一个:/bin/bash
  3. 安装oh-my-zsh: git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
  4. zsh --version查看zsh的版本,用chsh -s (which zsh) 切换shell至zsh,重启电脑
  5. 修改主题,vim ~/.zshrc修改配置文件,修改ZSH_THEME="ys",重启终端
  6. 安装插件。这个插件会记录你常去的那些目录,然后做一下权重记录,可以用这个命令看到你的习惯:j --stat,如果这个里面有你的记录,那你就只要敲最后一个文件夹名字即可进入,比如program:j program,就可以直接到:/usr/program
  7. 安装zsh-syntax-highlighting, 编辑配置文件,plugins=(zsh-syntax-highlighting)
    刷新下配置:source ~/.zshrc
  8. 安装powerline

使用时发现一个终端会记录其他终端的命令历史,这样使用向上箭头时,得到的不是希望的命令,这是因为zsh默认共享了命令历史,在~/.zshrc里添加 setopt no_share_history 即可解决

参考:安装oh-my-zsh

安装 unity tweak tool

1
sudo apt-get install unity-tweak-tool

启动:unity-tweak-tool

安装缺失文件:

1
2
3
sudo apt-get install notify-osd

sudo apt-get install overlay-scrollbar

添加桌面程序的启动

1
2
3
4
5
6
7
[Desktop Entry]
Version=1.0
Name=Groot
Exec=/usr/local/lib/groot/Groot
Icon=/home/user/Pictures/groot.ico
Type=Application
Terminal=false

如果运行后没有打开,很可能是执行有问题,单独运行Exec部分试试

设置系统快捷键

打开系统设置-Keyboard 键盘设置,以shutter的截图为例:

单击右侧 Disabled,然后快速按下 Ctrl+Alt+A就成功了

安装cairo-dock

实现类似Mac的效果

1
2
3
sudo add-apt-repository ppa:cairo-dock-team/ppa 
sudo apt-get update
sudo apt-get install cairo-dock cairo-dock-plug-ins

启动:cairo-dock

剪贴板命令

sudo apt-get install xclip
xclip可以将内容输出到‘X’的剪切板中, 命令echo "Hello, world" | xclip -selection clipboard,可以做成alias,这样很实用

更改鼠标风格

下载鼠标风格文件后,将整个文件夹放到/usr/share/icons,然后到Tweak Tool中就能选择了。

解决Ubuntu双显示器屏幕边缘鼠标粘滞问题

笔记本在ubuntu下使用了扩展屏幕,使得两个显示屏可以协同工作,但是每次鼠标在两个屏幕直接移动的时候,总是被屏幕边缘粘住,不让移到另一个屏幕上,除非以很快的速度移动才能把鼠标从一个屏幕移动到另一个屏幕上。

在桌面点击屏幕右上角账户名旁边的关机按钮,在弹出的菜单里选择”显示“,打开显示配置窗口,你会发现里面有一个选项叫做粘滞边缘,只要把这个选项关闭就可以了。

定制窗口侧边栏的默认书签

打开文件~/.config/user-dirs.dirs,然后修改如下:

1
2
3
XDG_DESKTOP_DIR="$HOME/桌面"
XDG_TEMPLATES_DIR="$HOME/"
XDG_PUBLICSHARE_DIR="$HOME/"

桌面最好不要修改,否则HOME下的文件都会显示在桌面上.再执行xdg-user-dirs-gtk-update,重启.

效果如下:

CPU跑满测试

安装stresssudo apt-get install stress

运行下面命令,可以使得6核CPU跑满,-c是指CPU,6是指6核,具体几核可以打开jtop查看。如用于其他jetson板卡,根据具体CPU核数更改下面命令即可。

1
stress -c 6


cmake教程(三)find_package和pkg_check_modules

find_package

find_package用于加载第三方库,可以将需要的部分指定为组件,例如

1
2
3
4
5
6
7
8
set(PACKAGE_DEPENDENCIES
cartographer_ros_msgs
eigen_conversions
geometry_msgs
urdf
visualization_msgs
)
find_package(catkin REQUIRED COMPONENTS ${PACKAGE_DEPENDENCIES})

  • REQUIRED可选字段:表示一定要找到包,找不到的话就立即停掉整个cmake。而如果不指定REQUIRED则cmake会继续执行。
  • COMPONENTS: 可选字段,表示查找的包中必须要找到的组件,如果有任何一个找不到就算失败,类似于REQUIRED,导致cmake停止执行。


使用Find_Package寻找模块时,每一个模块都会产生如下变量:

1
2
3
_FOUND
_INCLUDE_DIR
_LIBRARY or _LIBRARIES

如果_FOUND为真,需要把_INCLUDE_DIR加入到INCLUDE_DIRECTORIES中,_LIBRARY加入到TARGET_LINK_LIBRARIES

module 模式

find_package将先到module路径下查找 Find<name>.cmake。首先它搜索 ${CMAKE_MODULE_PATH}中的所有路径,然后搜索 /usr/share/cmake-3.5/Modules.比如find_package(Boost)搜索的文件是/usr/share/cmake-3.5/Modules/FindBoost.cmake

如果在CMakeLists.txt中没有下面的指令:

1
set(CMAKE_MODULE_PATH  "Findxxx.cmake文件所在的路径")

那么cmake不会搜索CMAKE_MODULE_PATH指定的路径,此时cmake会搜索第二优先级的路径.

如果cmake需要的是Find<name>.cmake,但是没在路径找到,可以添加路径到环境变量:

1
set(CMAKE_MODULE_PATH  ${CMAKE_MODULE_PATH} "Findxxx.cmake文件所在的路径")

config 模式

如果按照module模式未找到,cmake将会查找 <Name>Config.cmake<lower-case-name>-config.cmake 文件。cmake会优先搜索自定义的xxx_DIR路径。如果在CMakeLists中没有下面的指令:

1
set(xxx_DIR  "xxxConfig.cmake文件所在的路径")

那么cmake就不会搜索xxx_DIR指定的路径.而是到/usr/local/lib/cmake/xxx/中搜索,比如/usr/local/lib/cmake/yaml-cpp/yaml-cpp-config.cmake,如果还没有就失败了。一般情况下,对第三方库执行make install都会把cmake文件放到这个文件夹里,如果编译报错就需要找到文件然后手动复制过去,但是不是光复制xxxConfig.cmake,还要有xxxTargets.cmake,否则find_package就会报错:
缺abslTargets.cmake.png

上面两种添加路径到环境变量的方法要根据寻找的文件进行设置,比如我在QtCreator+Cmake中使用OpenCV,直接find_package(OpenCV REQUIRED)会报错 找不到OpenCVConfig.cmake ,先用locate尝试寻找FindOpenCV.cmakeOpenCVConfig.cmake,发现只有电脑上只存在后者,所以要用config模式: set(OpenCVDIR "/opt/ros/kinetic/share/OpenCV-3.3.1-dev")



按上面的方法一般都能寻找库成功,如果还是失败,就用下面这种 优先级最高的强制方法: </font>

1
find_package(OpenCV REQUIRED PATHS /usr/local/share/OpenCV NO_DEFAULT_PATH) 

相关的宏

cmake找到任意一个之后就会执行这个文件,然后这个文件执行后就会设置好一些cmake变量。比如下面的变量(NAME表示库的名字,比如可以用Opencv 代表Opencv库):

1
2
3
4
<NAME>_FOUND	# 布尔量
<NAME>_INCLUDE_DIRS or <NAME>_INCLUDES
<NAME>_LIBRARIES or <NAME>_LIBRARIES or <NAME>_LIBS
<NAME>_DEFINITIONS

我们可以在CMakeList中用下面代码检验find_package的结果:

1
2
3
4
5
6
7
8
9
10
11
find_package(but_velodyne  REQUIRED)
if (but_velodyne_FOUND)
MESSAGE (STATUS "definitions: ${ButVELODYNE_DEFINITIONS}")
MESSAGE (STATUS "include dirs: ${ButVELODYNE_INCLUDE_DIRS}")
MESSAGE (STATUS "lib dirs: ${ButVELODYNE_LIBRARY_DIRS}")

include_directories(${ButVELODYNE_INCLUDE_DIRS})
target_link_libraries (helloworld ${ButVELODYNE_LIBRARY_DIRS})
else()
MESSAGE (STATUS " but_velodyne not found")
endif(but_velodyne_FOUND)

如果cmake在两种模式提供的路径中没有找到对应的Findxxx.cmake和xxxConfig.cmake文件,此时系统就会提示最上面的那些错误信息。

ROS中的find_package

对于catkin_make,它会搜索ROS Package的安装目录,不必用set指定搜索目录.比如std_msgs对应的文件路径在/opt/ros/kinetic/share/std_msgs/cmake/std_msgsConfig.cmake.这两个文件是库文件安装时自己安装的,将自己的路径硬编码到其中。

ROS中常常需要多个package,比如roscpp,rospy,std_msgs。我们可以写成:

1
2
find_package(roscpp REQUIRED)
find_package(std_msgs REQUIRED)

这样的话,每个依赖的package都会产生几个变量,这样很不方便。所以还有另外一种方式:
1
2
3
4
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)

这样,它会把所有pacakge里面的头文件和库文件等等目录加到一组变量上,比如catkin_INCLUDE_DIRS,这样我们就可以用这个变量查找需要的文件了,最终就只产生一组变量了。


REQUIRED 参数:其含义是指是否是工程必须的,表示如果包没有找到的话,cmake的过程会终止,并输出警告信息。对应于Find.cmake模块中的 NAME_FIND_REQUIRED 变量。

COMPONENTS参数:在REQUIRED选项之后,或者如果没有指定REQUIRED选项但是指定了COMPONENTS选项,在它们的后面可以列出一些与包相关(依赖)的部件清单

编译ROS工作空间的问题

ROS的工作空间用的时间一长,就会创建很多package,有些package的编译又用到了其他的package,这时单纯使用catkin_make就会出现问题。比如package A用到了B,B里又用到了C。即使catkin_make --pkg也不行,因为它会把所有的package都处理一遍,此时处理到A时就会报错,会显示找不到B和C的cmake文件。它们的路径在devel/share/B/cmake

比较笨的方法就是在A的CMakeLists里先把B和C注释掉,同样在B的CMakeLists里把A注释掉,先用catkin_make --pkg C编译C,再依次编译B和A。

找了好长时间没找到很好的解决方法,最后只能预先编译好cmake文件放到SVN上,以后就算B和C的程序改变,也可以编译。

Qt库的情况

以上问题都还简单,问题是在ROS中调用Qt的情况,以Core模块为例,我们有下面的代码:

1
2
3
4
5
6
7
8
find_package(Qt5 COMPONENTS Core Xml)
target_link_libraries(bin Qt5::Core Qt5::Xml)

if(Qt5Core_FOUND)
MESSAGE(STATUS "##### ${Qt5Core_VERSION}")
MESSAGE(STATUS "##### ${Qt5Core_INCLUDE_DIRS}")
MESSAGE(STATUS "##### ${Qt5Core_LIBRARIES}")
endif(Qt5Core_FOUND)

运行结果是:

1
2
3
--  5.5.1
-- /usr/include/x86_64-linux-gnu/qt5/; /usr/include/x86_64-linux-gnu/qt5/QtCore; /usr/lib/x86_64-linux-gnu/qt5//mkspecs/linux-g++-64
-- Qt5::Core

首先注意:能使用Qt5的CMake最低版本是 3.1.0,Qt库的宏规则实际仍然符合cmake,不同的地方在于不需要`命令,只要target_link_libraries就足够,它会自动添加相应的include directories,compile definitions`等等

Qt5Core对应的cmake文件在Linux上有两个位置:

1
2
3
/home/user/Qt5.11.1/5.11.1/gcc_64/lib/cmake/Qt5Core/Qt5CoreConfig.cmake

/usr/lib/x86_64-linux-gnu/cmake/Qt5Core/Qt5CoreConfig.cmake

经过测试,CMakeLists中起作用的应当是第二个,应当是安装Qt时,在此目录生成了文件.这个路径与环境变量Qt5_DIR相近,在CMakeLists中用MESSAGE函数能看到:/usr/lib/x86_64-linux-gnu/cmake/Qt5

Boost

Boost比较特殊,cmake对它有特别照顾,使用命令cmake --help-module FindBoost可以看到极为详细的使用方法.使用Boost有时要加上REQUIRED COMPONENTS XXX,这是在搜索已经编译的库,但不会检查只有头文件的库.比如threadsystem要加入COMPONENTS但asio不需要.

cmake中使用Boost的filesystem,thread模块:

1
2
3
4
5
6
7
find_package(Boost COMPONENTS system filesystem thread REQUIRED)

target_link_libraries(mytarget
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
)

必须得加上system模块

pkg_check_modules

pkg_check_modules是 CMake 自己的 pkg-config 模块的一个用来简化的封装:你不用再检查 CMake 的版本,加载合适的模块,检查是否被加载,等等,参数和传给 find_package 的一样:先是待返回变量的前缀,然后是包名(pkg-config 的)。这样就定义了<prefix>_INCLUDE_DIRS和其他的这类变量,后续的用法就与find_package一致。

当安装某些库时(例如从RPM,deb或其他二进制包管理系统),会包括一个后缀名为 pc 的文件,它会放入某个文件夹下(依赖于系统设置,例如,Linux 为该库文件所在文件夹/lib/pkgconfig),并把该子文件夹加入pkg-config的环境变量PKG_CONFIG_PATH作为搜索路径。
pkg_check_modules实质上是检测系统中的 pkg-config 是否存在指定的 .pc 文件。

在我的电脑上执行echo $PKG_CONFIG_PATH,结果是:

1
/home/user/Robot/workspace/devel/lib/pkgconfig:/opt/ros/kinetic/lib/pkgconfig:/opt/ros/kinetic/lib/x86_64-linux-gnu/pkgconfig

bash.rc里没有设置,但是能获得这个环境变量,这是因为我们的环境变量里设置了

1
2
source /opt/ros/kinetic/setup.bash
source /home/user/Robot/workspace/devel/setup.bash



在cmake中使用pkg_check_modules时,就会去上面的路径里搜索pc文件,例如

1
2
3
4
# bfl (Bayesian Filtering Library)是一个使用pkg-config的第三方库
# 先搜索cmake自己的PkgConfig模块,才能使用pkg_check_modules
find_package(PkgConfig)
pkg_check_modules(BFL REQUIRED orocos-bfl)

其中PkgConfig的路径在/opt/ros/kinetic/share/ros/core/rosbuild/FindPkgConfig.cmake

搜索对应库文件,发现在以下路径:

1
2
/opt/ros/kinetic/lib/liborocos-bfl.so
/opt/ros/kinetic/lib/pkgconfig/orocos-bfl.pc

现在我们可以获得对应的宏,使用这个库了:

1
2
include_directories(${BFL_INCLUDE_DIRS})
link_directories(${BFL_LIBRARY_DIRS})

参考:
CMake Manual For Qt
Boost的编译库列表


Vim使用技巧

用Vim编辑新文件的时候, 由于服务器断开链接,会导致编辑很久的文件丢失,但是我们可以使用swp临时文件进行恢复, swp文件是隐藏文件. 比如文件路径下有以下文件.test.py.swp,可以使用vim -r test.py恢复文件test.py

设置主题

设置Vim为dracula主题,参考Dracula,在/etc/vim/vimrc的最后添加

1
2
3
packadd! dracula
syntax enable
colorscheme dracula

在普通状态下的命令

  • u: 返回旧状态
  • ctrl+r: 进入新状态
  • 点号: 重复上一次操作
  • dd: 删除光标所在行
  • ndd: 删除光标下面n行
  • yy: 复制光标所在行
  • nyy: 复制光标下面n行
  • 小写p: 将复制的内容在光标下一行粘贴

  • dw: 删除光标后的单词剩余部分

  • d$: 删除光标后的该行剩余部分
  • /string: 查找string,有结果后n查找下一个,N查找上一个
  • :set nu : 显示行号
  • 清空文件: 先执行gg跳至文件首行,再执行dG就清空了文件。

查找与替换

:s(substitute)命令用来查找和替换字符串。语法如下:
:{作用范围}s/{目标}/{替换}/{替换标志}

例如: 输入:进入命令模式,然后%s/foo/bar/g会在全局范围(%)查找foo并替换为bar,所有出现都会被替换(g)

查看和设置编码

:set fileencoding即可显示文件编码格式。

以在Vim中直接进行转换文件编码,比如将一个文件转换成utf-8格式
:set fileencoding=utf-8

用vim来设置UTF-8编码的BOM标记:

1
2
3
:set nobomb // 去掉BOM
:set bomb // 加上BOM
:set bomb? //查询当前UTF-8编码的文件是否有BOM标记

Vim 跳到某行

在编辑模式下输入ngg

vim +25 my_file.txt会直接打开文件,并将光标定位在第25 行

安装 Vim 主题

  1. 下载主题文件,比如solarized.vim
  2. 找到vim的runtime路径,在Vim的命令行模式输入 echo $VIMRUNTIME会显示。Ubuntu 17.10的是/usr/share/vim/vim80。它里面有colors文件夹,放入下载的主题文件。
  3. 修改.vimrc文件,它应该在/etc/vim目录,添加以下内容:
1
2
3
syntax enable
set background=dark
colorscheme solarized

然后重启Vim即可

配置Vim

/etc/vim/vimrc添加下面内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
set nu
colorscheme falcon

"禁止生成临时文件
set nobackup
set noswapfile

"搜索逐字符高亮
set hlsearch
set incsearch
set confirm
set background=dark
set autoread
set cursorline

hi CursorLine cterm=NONE ctermbg=darkred ctermfg=white
hi CursorColumn cterm=NONE ctermbg=darkred ctermfg=white

filetype plugin on

set showcmd " Show (partial) command in status line.
set showmatch " Show matching brackets.

配置Vim的光标颜色样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if &term =~ "xterm"
" INSERT mode
let &t_SI = "\<Esc>[5 q" . "\<Esc>]12;orange\x7"
" REPLACE mode
let &t_SR = "\<Esc>[3 q" . "\<Esc>]12;black\x7"
" NORMAL mode
let &t_EI = "\<Esc>[5 q" . "\<Esc>]12;red\x7"
endif
" 1 -> blinking block 闪烁的方块
" 2 -> solid block 不闪烁的方块
" 3 -> blinking underscore 闪烁的下划线
" 4 -> solid underscore 不闪烁的下划线
" 5 -> blinking vertical bar 闪烁的竖线
" 6 -> solid vertical bar 不闪烁的竖线

模板类和模板函数

定义模板类时,函数声明和实现都必须放在头文件里,否则编译失败,准确地讲,是在链接阶段报错.原因在于编译器虽然也编译了包含模板定义的源码文件,但是该文件仅仅是模板的定义,而并没有真正的实例化出具体的函数来。因此在链接阶段,编译器进行符号查找时,发现源码文件中的符号,在所有二进制文件中都找不到相关的定义,因此就报错了。
不过,函数的实现可以放在头文件的类外,不一定必须在类体内.

参考:
C++模板编译模型


容器类和allocator

new运算是先分配内存再执行构造函数,delete是先执行析构函数再释放内存。

STL allocator将这些操作做了精密分工:内存配置有alloc::allocate()负责,内存释放由alloc::deallocate()负责; 对象构造由::construct()负责,对象析构由::destroy()负责。

STL采用了两级配置器,当分配的空间大小超过128字节时,会使用第一级空间配置器,直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放;当分配的空间小于128字节时,为减少申请小内存造成的内存碎片和额外负担问题,将使用第二级空间配置器,它采用了内存池技术,通过16个free-list来配置和回收内存,free-list对内存的需求量按8的倍数处理,也就是16个free-list分别管理8,16,24……128字节的内存区块。


C++所有的标准容器类都接受一个allocator类作为其模板参数;这个参数有一个默认值,比如std::vector是 vector >的简写。

1
2
3
4
template <class T, class Alloc = alloc>  // 预设使用 alloc 为配置器
class vector {

}

1
2
std::string s="123";
printf("%s",s);

结果报错: cannot pass non-trivial object of type ‘std::string’ (aka ‘basic_string, allocator >’) to variadic function; expected type from format string was char*

也有可能不报错但是显示不正常,这跟编译器有关。


如何让回调函数与main函数互相传值

在main函数中访问回调函数中处理的变量

做法是让类的成员函数做回调函数,在回调函数中处理相应变量,在main函数中实例化类,然后通过返回对象的成员进行访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Listener 
{
public:
double theta1f;
double theta2f;
double theta3f;

void callback(const std_msgs::Float32MultiArray::ConstPtr& msg);
};

void Listener::callback(const std_msgs::Float32MultiArray::ConstPtr& msg)
{
theta1f = msg->data[0];
theta2f = msg->data[1];
theta3f = msg->data[2];
}

int main(int argc, char **argv)
{
ros::init(argc, argv, "node");
ros::NodeHandle nh;
ros::Rate loop_rate(30);
Listener listener;
ros::Subscriber sub = nh.subscribe<std_msgs::float32multiarray>("topic_subscribed", 1, &Listener::callback, &listener);
while (ros::ok())
{
ros::spinOnce();
ROS_INFO("This is theta1f: %.2f", listener.theta1f);
ROS_INFO("This is theta2f: %.2f", listener.theta2f);
ROS_INFO("This is theta2f: %.2f", listener.theta2f);
loop_rate.sleep();
}

return 0;
}



回调函数中访问main函数中的变量赋值

以前我都是用全局变量来实现,但这种方法总感觉不保险,后来发现ROS中有专门的解决方法,NodeHandle::subscribe有一个重载可以处理这种情况,用到了boost::bind,其实用std::bind也能实现。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <ros/ros.h>
#include <std_msgs/Int8.h>
//回调函数里访问main中的一个结构体
typedef struct _Struct
{
int m;
double n;
} myStruct;

void callback(const std_msgs::Int8ConstPtr& msg, myStruct p)
{
ROS_INFO("msg: %d", msg->data);
ROS_INFO("m: %d",p.m);
ROS_INFO("n: %f",p.n);
sleep(1);
}


int main(int argc, char** argv)
{
ros::init(argc,argv, "Sub");
ros::NodeHandle nh;

myStruct p;
p.m = 7;
p.n = 1.85;

// "_1" 是占位符,对应 void callback(const std_msgs::Int8ConstPtr& msg, myStruct p)第一个参数
/* subscribe (
* const std::string &base_topic,
* uint32_t queue_size,
* const boost::function< void(const std_msgs::Int8ConstPtr &)> &callback )
*/
ros::Subscriber sub = nh.subscribe<std_msgs::Int8>("topic", 20, boost::bind(&callback, _1, p) );
ros::spin();
return 0;
}

myStruct是我自定义的结构体,在main函数里对其声明的变量赋值,回调的形参里多了它的变量。
其中subscribe函数的原型是这个:

1
2
3
4
5
6
7
template<class M >
Subscriber ros::NodeHandle::subscribe(const std::string& topic,
uint32_t queue_size,
const boost::function< void(const boost::shared_ptr< M const > &)> & callback,
const VoidConstPtr & tracked_object = VoidConstPtr(),
const TransportHints & transport_hints = TransportHints()
)

boost::function对象能接受函数和仿函数, boost::bind创建了仿函数,包含了所有想传给回调函数的参数。

与平时的重载形式不同,这次要用到话题的模板类型,第三个形参不是回调函数的指针,而是boost::bind,其中_1是一个占位符,要传给回调函数参数,在发生函数调用时才接受实参;最后是结构体的变量。

注意:回调函数第一个形参必须是const std_msgs::Int8ConstPtr&形式,其他形式比如const std_msgs::Int8Ptr&会报错,另外看到一篇博客说这里不能用引用,但是我用引用也没报错。对于回调函数callback,一般在bind里不用&,但是用了也可以,因为bind可接受函数或函数指针

参考:
How to make callback function called by several subscriber?
How to pass arguments to/from subscriber callback functions


使用ROS Service(二) 代码演示

程序演示

下面是我写的一个服务程序,服务文件ctrl.srv的构成为:

1
2
3
int8 cmd
---
bool ret

客户端发出命令,如果cmd!=0,服务端会打开摄像头程序;如果cmd=0,服务端会关闭摄像头程序。

注意:最好是将所有自定义的服务和消息文件放到一个单独的package里面,否则会出现一个package的修改会影响到另一个用到它的消息/服务的package的编译.

新建srv文件后,容易忘记在CMakeLists里添加这个文件,导致编译失败

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <ros/ros.h>
#include <riki_msgs/ctrl.h>

bool control(riki_msgs::ctrl::Request &req, riki_msgs::ctrl::Response &res)
{
if(req.cmd==0)
{
ROS_INFO("shutting down camera");
system("rosnode kill /usb_cam");
ROS_INFO("close camera done");
res.ret = 0;
}
else
{
ROS_INFO("starting camera");
system("roslaunch usb_cam usb_cam.launch & ");
ROS_INFO("camera is up");
res.ret = 1;
}
}

int main(int argc, char** argv)
{
ros::init(argc,argv,"camServer");
ros::NodeHandle nh;
ros::ServiceServer server = nh.advertiseService("control_cam",control);
ROS_INFO("------ waiting for client's request ------");
ros::spin();
return 0;
}

可以看出服务端的程序与话题中的订阅者程序高度相似,control函数就是个回调函数。

system函数调用roslaunch时,由于是在fork出的子进程里执行,launch的节点会一直阻塞不返回,在命令最后加&,让子进程返回

service回调函数里只能return truefalse,若return整数会导致客户端的call结果为false,但实际成功,这样会影响判断。

service客户端发命令后,出现报错 ERROR: service [/service_name] responded with an error: b’’ ,原因在于service服务端的回调函数必须 return true


advertiseService的部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool ServiceManager::advertiseService(const AdvertiseServiceOptions& ops)
{
boost::recursive_mutex::scoped_lock shutdown_lock(shutting_down_mutex_);
if (shutting_down_)
{
return false;
}
{
boost::mutex::scoped_lock lock(service_publications_mutex_);
// 如果service已经发布,就报错,然后返回,其实这里改成报警比较合适
if (isServiceAdvertised(ops.service))
{
ROS_ERROR("Tried to advertise a service that is already advertised in this node [%s]", ops.service.c_str());
return false;
}

如果涉及到耗时的工作,回调函数应该这样写

1
2
3
4
5
6
bool serviceCB(mow_msgs::task::Request &req, mow_msgs::task::Response &res)
{
// ......
res.ret = "to charge";
// 耗时的工作
}

如果把耗时的工作放在res之前,那么客户端提前退出时(比如ctrl+C),会报错没有收到返回值,虽然不影响,但是不优雅。

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <ros/ros.h>
#include <riki_msgs/ctrl.h>

int main(int argc, char** argv)
{
ros::init(argc, argv, "controlCam");
if(argc!=2)
{
ROS_INFO("client need command");
return 1;
}
ros::NodeHandle nh;
ros::ServiceClient client = nh.serviceClient<riki_msgs::ctrl>("control_cam");
riki_msgs::ctrl srv;
srv.request.cmd = atoi(argv[1]);
if(client.exists())
{
ROS_INFO("service control_cam is up");
ROS_INFO("service name:%s",client.getService().c_str());
}
else
{
ROS_ERROR("service control_cam is not available");
return 1;
}
if(client.call(srv))
{
ROS_INFO("client calling srv !");
if(srv.request.cmd>0)
ROS_INFO("Sending command start!");
else
ROS_INFO("Sending command stop!");
}
else
{
ROS_ERROR("client calls srv failed !");
return 1;
}
// 调用成功以后才能获取response
cout<<"服务端的返回值: "<<srv.response.ret<<endl;
return 0;
}

构建client的时候后面的路径要写绝对路径,有时候需要加个 /

客户端的程序与话题发布者的程序高度相似,exists()检查服务端的服务是否启动,若未启动则返回。call调用了服务,成功会返回true而且调用完成后,可以在客户端程序里获得服务端的返回值

最后,先运行服务端,然后运行客户端,如果是rosrun control_cam controlCam 1则在服务端所在终端启动摄像头程序,若是0则关闭。

另一种使用方式

1
2
3
4
5
6
7
8
9
std_srvs::Empty srv;
ros::service::call("/move_base_node/clear_costmaps", srv);

while(!ros::service::call("static_map", req, resp))
{
ROS_WARN("Request for map failed; trying again...");
ros::Duration d(0.5);
d.sleep();
}

常用函数

ros::ServiceClient常用函数:

1
2
3
4
5
6
7
8
9
bool call (Service &service)  //调用service,client发起通信。成功返回true

bool exists () //检查相应名称的服务是否可用

std::string getService () //返回客户端通信的服务名称
// 单例模式
static const ServiceManagerPtr & instance ()

bool unadvertiseService (const std::string &serv_name)

如果要解除service,用法是ros::ServiceManager::instance()::unadvertiseService,也就是使用单例模式进行全局访问。 API说isServiceAdvertised可以判断某service是否发布,但可惜是private

问题


切换算法时,会有个报警 Tried to advertise a service that is already advertised in this node。这个其实无任何影响,报警在ROS底层的源码,原因在于GlobalPlanner::initialize函数有一句:

1
make_plan_srv_ = private_nh.advertiseService("make_plan", &GlobalPlanner::makePlanService, this);

这里是已经注册了service,如果想要去掉这个报警,可以这样改:
1
2
3
4
std::string makePlanServiceName = "/move_base/"+name+"/make_plan";

ros::ServiceManager::instance()->unadvertiseService(makePlanServiceName);
make_plan_srv_ = private_nh.advertiseService("make_plan", &GlobalPlanner::makePlanService, this);

其中的bool ros::ServiceManager::unadvertiseService(const std::string& serv_name)作用是 Unadvertise a service. This call unadvertises a service, which must have been previously advertised, using advertiseService().

md5不匹配

调用service时报错md5不匹配,其实是调用失败了,call的返回值是false,首先应检查客户端定义是否正确和服务是否存在:

客户端一直阻塞

我在move_base.cpp里写了一个函数 func,是rosservice的客户端,调用service clear_costmaps。在MoveBase的其他函数里,调用这个函数,结果发现会一直阻塞。后来注意到服务端程序就是MoveBase::clearCostmapsService,也就是在同一个类里

客户端call service阻塞的原因只有两个:

  1. 多个客户端call 同一个service,而服务端一次只能处理一个请求
  2. 服务端程序没有return值

服务端程序是MoveBase的,而且有了返回值,那么只可能是第一个原因了。

参考:blocking service callbacks


使用ROS Service(一) 服务和话题的区别,基本命令

服务和话题

特点 Topic Service
通信方式 异步通信 同步通信
通信模型 Publish-Subscribe Request-Reply
多对多 多对一
接收者接到数据会回调 远程过程调用(RPC)服务器端的服务
应用场景 连续,高频的数据发布 偶尔调用的功能
举例 激光雷达 开关传感器,拍照,逆解计算
由于是同步通信,service 的回调不能写复杂的、耗时长的业务

rosservice call

rosservice call addTwoNum 3 4: 调用服务,addTwoNum是服务名称, 名称不加引号,3和4是服务中的req变量的值,注意必须按服务文件中的顺序赋值。

但上面是简单情况,srv文件稍微有点复杂时,就会怎么填也不对,个人认为这是ROS Service很失败的一个地方。
例如srv文件的请求部分是这样的:

1
2
string request_string
string album

我试了很多种组合,都不正确。其实这种情况也好办,在call srvname之后直接Tab,会给出一个模板:
1
2
rosservice call service_name "request_string: ''
album: ''"

我们要做的就是把内容填到单引号里面,不要再自己修改模板,否则容易出错。

call执行成功时,终端不会有任何结果。但是经常出现这样的错误: ERROR: service [/control_cam] responded with an error: ,但程序执行没有问题。原因是 程序中对srv文件中的应答没有处理

其他常用命令:

  • rosservice list: 显示当前所有活动的服务
  • rosservice info addTwoNum: 显示服务的信息
  • rosservice type addTwoNum: 显示服务的变量类型

其实服务的相关命令和话题的很类似

ROS的服务类型

启动乌龟节点时,有一个服务叫clear,类型:

1
2
3
rosservice type clear

std_srvs/Empty

看源码可知,服务的类型为空,这表明调用这个服务不需要参数(比如,请求不需要发送数据,响应也没有数据).调用后,服务清除了turtlesim_node的背景上的轨迹,没有响应.

std_srvs包还有两个服务:std_srvs/Triggerstd_srvs/SetBool,但是很不常用


locate和find
  • locate的速度比find快,因为它并不是真的查找文件,而是查数据库。

  • 新建的文件,我们立即用locate命令去查找,一般是找不到的,因为数据库的更新不是实时的,而是每天

  • locate命令所搜索的后台数据库在/var/lib/mlocate这个目录下,可能有些Linux系统位置不同,具体我们可以用locate locate查询
  • 并不是所有的目录下的文件都会用locate命令搜索到,/etc/updatedb.conf这个配置文件中,配置了一些locate命令的一些规则。

  • updatedb会大致每天运行,这是靠系统的crontab命令实现的

  • updatedb -U:更新指定目录相关的数据库信息。默认是整个系统,耗时比较长,因此可以使用该参数,比如sudo updatedb -U /home/user/

updatedb的配置文件 /etc/updatedb.conf

1
2
3
4
5
cat /etc/updatedb.conf 
PRUNE_BIND_MOUNTS = "yes"
PRUNEFS = "9p afs anon_inodefs auto autofs bdev binfmt_misc cgroup cifs coda configfs cpuset debugfs devpts ecryptfs exofs fuse fuse.sshfs fusectl gfs gfs2 gpfs hugetlbfs inotifyfs iso9660 jffs2 lustre mqueue ncpfs nfs nfs4 nfsd pipefs proc ramfs rootfs rpc_pipefs securityfs selinuxfs sfs sockfs sysfs tmpfs ubifs udf usbfs fuse.glusterfs ceph fuse.ceph"
PRUNENAMES = ".git .hg .svn"
PRUNEPATHS = "/afs /media /mnt /net /sfs /tmp /udev /var/cache/ccache /var/lib/yum/yumdb /var/spool/cups /var/spool/squid /var/tmp /var/lib/ceph"

  • PRUNENAMES 搜索时不搜索的文件类型
  • PRUNEPATHS 搜索时不搜索的路径
  • PRUNE_BIND_MOUNTS = "yes" 开启搜索限制
  • PRUNEFS 搜索时不搜索的文件系统

locate 常用命令

1
2
3
4
5
6
7
locate -c   # 查询指定文件的数目。(c为count的意思)
locate -e # 只显示当前存在的文件条目。(e为existing的意思)
locate -i # 查找时忽略大小写区别

# 使用正则表达式查找文件
locate -r makefile$ # 以makefile结尾的文件
locate -r ^/home/user/ # 以/home/user/开头的文件

从结果中取出词尾是config2

注意:locate的结果可能是不存在的文件,这时最好用locate -e

locate 查找文件tree.xml,也就是知道完整的文件名时,那么最好用 locate /tree.xml,如果不加/,会显示test_tree.xml的结果

find 常用命令

查找当前目录中(包括子目录)所有扩展名为cfg的文件:

1
2
# 或者 '*.cfg'
find -name *.cfg

  • find . -name '*.cpp' -mmin -30 当前目录下,最近30分钟修改的cpp文件

  • find . -name '*.cpp' -mtime 0 当前目录下,最近24小时修改的cpp文件

  • find . -type f -mtime 0 当前目录下,最近24小时修改的常规文件