🐠

fish shellでROS1/2の設定

commits6 min read 3

みなさんはfishという shell をご存知でしょうか?

https://fishshell.com

fish とはfriendly interactive shell の略で、その名の通り friendly つまり使いやすさを謳った shell です.個人的にはあまり設定を頑張らなくてもそれなりに便利に使えるといった感じで、そういうの面倒だなと感じている人におすすめです.

一応欠点も書いておくと、POSIX 互換ではないので sh や bash のスクリプトは普通には動かせないことや、bash や zsh に比べると情報が少ないというのがあります.前者についてはbass というパッケージがありますし、インストールコマンドぐらいならそのときだけ bash に切り替えればよいので、個人的にはあまり困っていません.後者についても、少ないといってもドキュメントがありますし、調べるとなんだかんだ Stack Overflow あたりが引っかかるので、英語がある程度読めればあまり困らないと思います.

fish の入門記事を書くつもりはないので、詳しく知りたい方は「fish shell」とかで調べて手頃な記事を読んでみてください.何か記事を紹介できるとよいのですが、自分が最初に読んだ記事も大分記述が古くなってきている[1]のであまり勧められるものがなかったりします…….

ということで早速本題に.あまり fish を使っている人自体少ないのではと思いますが、「ROS1/2 使うから fish は我慢して bash/zsh 使っています」みたいな人に向けて、fish でも ROS できるよということをお伝えしつつ、あわよくば fish 使う人増えたらいいなみたいな記事です.

準備

ユーティリティ関数として、以下の command_exist だけ用意しておきます.ほぼ見たとおりだと思いますが、第 1 引数のコマンドが存在するかどうかを調べる関数です.

function command_exist
    if type $argv[1] >/dev/null 2>&1
        return 0
    else
        echo $argv[1]: not found
        return 1
    end
end

以下のように使います.

$ if command_exist echo; echo OK; end
OK
$ if command_exist xxx; echo OK; end
xxx: not found

ROS1

bash のスクリプトを読みこむために、以下のパッケージがあると便利なので README に沿ってインストールします.

https://github.com/edc/bass

自分は何となく最初に fisher を入れて、特に困っていないのでそのまま使っていますが、oh-my-fish や fundle でもいいと思います.

https://github.com/jorgebucaran/fisher

https://github.com/oh-my-fish/oh-my-fish

https://github.com/danhper/fundle

ROS1 の設定は以下の通りです.

ros1.fish
set -l dists
set -a dists kinetic # Ubuntu 16.04
set -a dists melodic # Ubuntu 18.04
set -a dists noetic # Ubuntu 20.04

for dist in $dists
    set -l path /opt/ros/$dist/setup.bash
    if test -f $path
        bass source $path
    end
end

if command_exist roscore
    if set -q ros1_ip
        set -x ROS_HOSTNAME $ros1_ip
    else
        set -x ROS_HOSTNAME localhost
    end

    set -x ROS_MASTER_URI http://$ROS_HOSTNAME:11311

    set -l path /opt/ros/$ROS_DISTRO/share/rosbash/rosfish
    if test -f $path
        source $path
    end

    set -l path ~/catkin_ws/devel/setup.bash
    if test -f $path
        bass source $path
    end
end

一応部分毎に解説を.

set -l dists
set -a dists kinetic # Ubuntu 16.04
set -a dists melodic # Ubuntu 18.04
set -a dists noetic # Ubuntu 20.04

for dist in $dists
    set -l path /opt/ros/$dist/setup.bash
    if test -f $path
        bass source $path
        break
    end
end

set -lはローカル変数です.-aは配列に対する append で、ちなみに-pが prepend です.何となくの可読性でこう書いていますが、下のように一気に構築したり、変数を介さなくてもよいです.

set -l dists kinetic melodic noetic
for dist in kinetic melodic noetic

for文に関してはdodoneがなくてわかりやすくないでしょうか?

(ところで、改めて見ると少し Ruby っぽいなと思いました.個人的に Ruby の文法は結構好きなので、その共通点に惹かれたところもあるのかもしれません.Ruby は for 自体あまり書かないとかはさておき.)


    if set -q ros1_ip
        set -x ROS_HOSTNAME $ros1_ip
    else
        set -x ROS_HOSTNAME localhost
    end

    set -x ROS_MASTER_URI http://$ROS_HOSTNAME:11311

set -qで変数の存在チェックをしています.fish にはユニバーサル変数という変数があり、これは全てのセッションで共有され、ログアウト等しても保持され続けます.この性質からシェル等の設定変数として使うことができます.
まさにros1_ipがそのユニバーサル変数としてセットされることが想定されており、set -U ros1_ip xxx.xxx.xxx.xxxとすると、このスクリプト自体の評価タイミングがあるので即座とはいかないものの、新たにタブを開いたり、既に開いているものでもexec fish等でリロードすれば設定が反映されるので非常に便利です.

  • ユニバーサル変数についての参考

https://fishshell.com/docs/current/#more-on-universal-variables
    set -l path /opt/ros/$ROS_DISTRO/share/rosbash/rosfish
    if test -f $path
        source $path
    end

roscd等のコマンドを使えるようにするためと、補完を効かせるために読み込みます.これは fish スクリプトなので、bass は不要です.一時期スクリプトが壊れていて GitHub から最新のファイル取ってこないと動かないこともありましたが、最近は安定しています
と思っていたら補完が上手く動かなくて、git log 見てたらバグ見つけたので PR 送りました.

https://github.com/ros/ros/pull/279
    set -l path ~/catkin_ws/devel/setup.bash
    if test -f $path
        bass source $path
    end

こちらは fish スクリプトが吐かれないので、bass を使って読み込みます.

ROS2

ros2.fish
set -l dists
set -a dists dashing # Ubuntu 18.04
set -a dists eloquent # Ubuntu 18.04
set -a dists foxy # Ubuntu 20.04

for dist in $dists
    set -l path /opt/ros/$dist/setup.bash
    if test -f $path
        bass source $path
    end
end

if command_exist ros2
    if command_exist register-python-argcomplete
        register-python-argcomplete --shell fish ros2 | source
    end

    set -l path ~/ros2/install/setup.bash
    if test -f $path
        bass source $path
    end
end

ほぼ ROS1 と同じなのでほとんど省略しますが一箇所だけ.

    if command_exist register-python-argcomplete
        register-python-argcomplete --shell fish ros2 | source
    end

register-python-argcompleteは argcomplete という PyPi パッケージの一部で、コマンド引数を扱うのに便利な標準ライブラリである argparse からコマンド補完を作ってくれるやつです.

https://pypi.org/project/argcomplete/

argcomplete は fish をサポートしてくれているので、パイプを通してsourceで読み込めばよいです.

ROS1 + ROS2

ROS1、ROS2 のどちらかだけを使うならこれらで十分ですが、自分は dotfile を GitHub に上げて複数の環境で共有しているので、様々な環境で使いやすいようにしました.
まず、ROS1、ROS2 それぞれの設定ファイルを、ros1.fish, ros2.fishとして切り出して、以下のros.fishから条件に応じて呼び分けています.

ros.fish
set -e ROS_DISTRO

function ros1_load
    source $fish_scripts_dir/ros1.fish
end

function ros2_load
    source $fish_scripts_dir/ros2.fish
end

if set -q use_only_ros1 && test $use_only_ros1 -ne 0
    source $fish_scripts_dir/ros1.fish
    echo "ROS1 (ROS_MASTER_URI: $ROS_MASTER_URI)"
else if set -q use_only_ros2 && test $use_only_ros2 -ne 0
    source $fish_scripts_dir/ros2.fish
    echo ROS2
end

use_only_ros1(2)はまたもやユニバーサル変数です.この環境ではどちらか片方しか使わないと決まっていれば、変数をセットしておいて起動時に設定を読み込みます.
どちらも使う場合、勝手に読まれても困るので、ros1(2)_loadを明示的に読み込むようにしました.

おわりに

rosfishバグがなければ、fish でもちゃんと補完が効くので、fish 本来の使いやすさと相まって結構快適に動かせると思います.特に ros2 の方は argcomplete を使っているので、rosfishよりも安定しているでしょう.

脚注
  1. fish は現在 3 系ですが 2 と 3 で大きく変わりましたし、パッケージマネージャの fisher のインストールコマンドは 2 回変わりました.install(v2) => add(v3) => install(v4)(なぜ戻した).パッケージ名もちょくちょく変わっていたり. ↩︎

この記事に贈られたバッジ