launchファイルでワイルドカードを使う


ROSのlaunchファイルでは,ワイルドカード(グロブ, *.bag のような表現)が使えません.例えば,気持ち的には

<?xml version="1.0"?>
<launch>
  <arg name="bag_dir" default="." />

  <node name="rosbag_player" pkg="rosbag" type="play"
    args="$(arg bag_dir)/*.bag" />
</launch>

というようなことがやりたいのですが, *.bag は展開されずに文字列として rosbag ノードの引数になってしまいます.

これを 無理矢理 展開させるようにする方法を本記事で紹介します.かなりHackyな方法なので,推奨はしません.

TL;DR

Pythonの標準モジュールの一つである glob をインポートして,展開します.ただし, import glob__import__("glob") が使用不可なので,eval をネストすることでインポートしています.

<?xml version="1.0"?>
<launch>
  <arg name="bag_dir" default="." />
  <arg name="bag_path" value="$(eval eval('_' + '_import_' + '_(&quot;glob&quot;).glob(' + 'bag_dir + &quot;/*.bag&quot;)[0]'))" />

  <node name="rosbag_player" pkg="rosbag" type="play"
    args="$(arg bag_path)" />
</launch>

解説

本当なら以下のようにしたいですよね.

<?xml version="1.0"?>
<launch>
  <arg name="bag_dir" default="." />
  <arg name="bag_path" value="$(eval import glob; glob.glob(bag_dir + '/*.bag')[0]))" />

  <node name="rosbag_player" pkg="rosbag" type="play"
    args="$(arg bag_path)" />
</launch>

でも,Pythonでは eval("import glob") が不正なので,エラーが出ます.

RLException: Invalid <arg> tag: invalid syntax (<string>, line 1).

じゃあ __import__("glob") でいいじゃない.

<?xml version="1.0"?>
<launch>
  <arg name="bag_dir" default="." />
  <arg name="bag_path" value="$(eval __import__('glob').glob(bag_dir + '/*.bag')[0])" />

  <node name="rosbag_player" pkg="rosbag" type="play"
    args="$(arg bag_path)" />
</launch>

しかし,roslaunchのevalではアンダースコアを二つ含む表現を使ってはいけない模様.

RLException: Invalid <arg> tag: $(eval ...) may not contain double underscore expressions.

ならばevalをevalしてアンダースコアを連結させるしかない.

<?xml version="1.0"?>
<launch>
  <arg name="bag_dir" default="." />
  <arg name="bag_path" value="$(eval eval('_' + '_import_' + '_(&quot;glob&quot;).glob(' + 'bag_dir + &quot;/*.bag&quot;)[0]'))" />

  <node name="rosbag_player" pkg="rosbag" type="play"
    args="$(arg bag_path)" />
</launch>

XMLのダブルクォートのせいで &quot; を使っていますが,実行しているPythonコードは以下の通りです.

eval(
  eval(
    '_' + '_import_' + '_("glob").glob(' + 'bag_dir + "/*.bag")[0]'
  )
)

展開されたあと,roslaunchが実際に実行するコードは

__import__("glob").glob(bag_dir + "/*.bag")[0]

で, bag_dirroslaunchのarg展開により,argとして事前に設定されている bag_dir に展開されます.

おまけ

[0] を変えることで複数マッチがあった場合にどれを使うかや, /*.bag を変えることでグロブ表現を変えることもできます.これらをargにして展開するようにしたら親切かも……?

<?xml version="1.0"?>
<launch>
  <arg name="bag_dir" default="." />
  <arg name="index" default="0" />
  <arg name="glob_pattern" default="*.bag" />
  <arg name="bag_path" value="$(eval eval('_' + '_import_' + '_(&quot;glob&quot;).glob(' + 'bag_dir + &quot;/&quot; + glob_pattern)[index]'))" />

  <node name="rosbag_player" pkg="rosbag" type="play"
    args="$(arg bag_path)" />
</launch>

おわりに

可読性がかなり低いので,利用するときは気をつけましょう...