Home Blog Simulation Papers

How to use import statement inside of an eval in a ROS roslaunch file

2021-02-05

Since ROS Kinetic (Ubuntu 16.04), roslaunch allows you to eval arbitrary Python expressions.

For example, you can do something like this to manipulate parameters found in two different files:

<arg name="parameter" default="$(eval float(open('config1').read().strip()) + float(open('config2').read().strip()))"/>

Something I've always wanted to do is to check for the existence of a file. However, since the os module has not been imported in roslaunch's eval environment, this seems impossible to do. Furthermore, roslaunch explicitly forbids double underscores, precluding the use of an expression import with __import__. However, it does so by searching for __! See line 345 at the roslaunch source, which references https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html.

We can get around this by nesting evals and creating the __import__ method using string addition of individual underscores.

<arg name="conf" default="$(eval 'b.txt' if eval('_' + '_import_' + '_(\'os\')').path.exists('b.txt') else 'a.txt')"/>

Now, the argument named conf will have the value b.txt if a file named b.txt exists in the filesystem and a.txt otherwise. This may be useful when you wish to use an alternative configuration file if it exists, but fall back to a default configuration file if it doesn't.

While this certainly looks messy, you can break more complicated expressions into sub arguments. Below is an example from a launch file I use.

<arg name="imp" default="eval('_' + '_import_' + '_')"/>
<arg name="fn"  default="find('vehicle_configs') + '/' + vehicle + '/' + arg('controlconfig_name') + '.yaml'"/>
<arg name="ldc" default="eval(arg('imp') + '(\'yaml\')').safe_load(open(eval(arg('fn'))))"/>
<arg name="ex"  default="eval(arg('imp') + '(\'os\')').path.exists(eval(arg('fn')))"/>
<arg name="controller_type" default="$(eval eval(arg('ldc')).get('controller', arg('default_controller_type')) if eval(arg('ex')) else arg('default_controller_type'))"/>