fish shell で自作コマンド(プラグイン)を作ってみよう
本記事の内容
ただHello, World!
するだけのすごくシンプルなコマンドを作りながら、以下の内容を簡単に説明していきます。
- オプションのパース
- tab補完の設定
- テストの作成
- GitHub actionsでのテストの実行
- fisherを使って出来たコマンドをインストールしてみる
自作コマンドを作れるようになると、ターミナル生活が少し豊かになります。また、「こうこうこうしたいんだけど具体的にどうスクリプトを書けば良いか分からない」といった場合には、ChatGPTに聞くと結構な確率で教えてくれるのでおすすめです。面倒な作業はAIにやらせましょう。
実際にコマンドを作ってみよう
リポジトリの作成
まずはコマンドを開発するリポジトリを作ります。
mkdir helloworld.fish
cd helloworld.fish
git init
続いて、以下のディレクトリとファイルを作成します。
ディレクトリ・ファイル | 説明 |
---|---|
functions/ | コマンドを保存するディレクトリ |
functions/helloworld.fish | コマンドの処理を書くスクリプトファイル |
completions/ | コマンドの補完の設定を保存するディレクトリ |
completions/helloworld.fish | コマンドの補完の設定を書くスクリプトファイル |
mkdir functions
mkdir completions
touch functions/helloworld.fish
touch completions/helloworld.fish
ついでに、README.mdとLICENSEも作っときます。LICENSEの内容は適宜好きなようにすると良いでしょう。
echo "# A command to print 'Hello, World!'" > README.md
touch LICENSE
スクリプトを実際に書いていく
実際にコマンドを実行した際に行われる処理をスクリプトファイルへ書いていきます。functions/helloworld.fish
へ次の内容を記述してみましょう。
function helloworld
echo "Hello, World!"
end
コマンドを実行した際に、helloworld関数が呼び出されます。
出来たコマンドをインストールしてみる
fish shellでは、~/.config/fish/functions
下にあるスクリプトファイルをコマンドとして認識します。毎回手動でコピーして動作確認しても良いですが、面倒なので今回はfisherというプラグインマネージャーを使います。
まずはfisherをインストールします。
curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher
次に、実際に先ほど書いたコマンドをインストールします。
# プロジェクトのルートディレクトリにいるか確認する
$ pwd
/your/path/to/helloworld.fish
# 実際にインストールする
$ fisher install .
fisher install version 4.3.5
Installing /your/path/to/helloworld.fish
~/.config/fish/functions/helloworld.fish
~/.config/fish/completions/helloworld.fish
Installed 1 plugin/s
実際にコマンドを実行してみて、動作確認してみる。
$ helloworld
Hello, World!
また、変更を反映させたい場合には、次のコマンドを実行します。
fisher update /your/path/to/helloworld.fish
次のワンライナーでも同様のことを出来るので、適当にエイリアスを登録しておくと便利かも?しれません。
fisher update (git rev-parse --show-toplevel 2>/dev/null)
コマンドに機能追加していく
-h,--help
オプションの実装
helloworld関数の冒頭に、argparseコマンドを記述することで、オプションを解析できるようにします。argparseコマンドは、与えられたコマンドライン引数に指定したオプションがある場合には、それを示す変数を生成します。次のコードでは、-h,--help
が引数に与えられた場合には、_flag_help
変数が生成されます。
function helloworld
+ argparse --name=helloworld \
+ h/help \
+ -- $argv or return
echo "Hello, World!"
end
helpオプションが与えられた場合には、helpメッセージが表示されるようにします。set -ql
コマンドで_flag_help
変数が存在するかどうかを調べることで、helpメッセージを表示するかどうかを判断します。
function helloworld
argparse --name=helloworld \
h/help \
-- $argv or return
+ if set -ql _flag_help
+ echo "Usage: helloworld"
+ echo ""
+ echo "Options:"
+ echo " -h, --help Show this help message and exit"
+ return 0
+ else
echo "Hello, World!"
+ return 0
+ end
+
+ return 1
end
実際に動作確認をしてみる。
# 変更を反映させる
$ fisher update /your/path/to/helloworld.fish
# 実行する
$ helloworld --help
Usage: helloworld
Options:
-h, --help Show this help message and exit
補完の設定を書く
completions/helloworld.fish
に次の内容を書き込む。
# tabでファイル名が補完されるのを無効化する
complete --command=helloworld --no-files
# -h, --help が補完されるようにする
complete --command=helloworld --short=h --long=help --description="Show help message."
これでtab補完が効くようになっているはずです。
テストしてみる
テスト用のディレクトリとファイルを作る
まずはテスト用にtest
ディレクトリを作り、その中にhelloworld.fish
を作ります。そしてhelloworld.fish
にテストを記述していきます。
mkdir test
touch test/helloworld.fish
まずはテストが成功しているかどうかの状態を保持するis_success
変数を宣言します。
#!/usr/bin/env fish
set is_success true
そして、得られた結果と期待される結果が一致するかを確認し、一致しない場合にはis_success
変数をfalse
にするassert
関数を作ります。
#!/usr/bin/env fish
set is_success true
+ function assert
+ set expected $argv[1]
+ set actual $argv[2]
+ if [ "$expected" != "$actual" ]
+ echo "ERROR: expected '$expected', got '$actual'"
+ set is_success false
+ else
+ echo "SUCCESS: expected '$expected', got '$actual'"
+ end
+ end
実際にコマンドを実行して、期待される結果を得られるかテストするコードを書く。
#!/usr/bin/env fish
set is_success true
function assert
# 省略...
end
+ echo "=== START TESTS ==="
+
+ # * test no args
+ set expected "Hello, World!"
+ set actual (helloworld)
+ assert "$expected" "$actual"
+
+ # * test -h,--help
+ set expected "Usage: helloworld\n\nOptions:\n -h, --help Show this help message and + exit"
+ set actual (helloworld --help | string join "\n")
+ assert "$expected" "$actual"
+
+ set actual (helloworld -h | string join "\n")
+ assert "$expected" "$actual"
+
+ echo "=== END TESTS ==="
いくつかのテストケースで失敗した場合には、exit 1
するようにする。
#!/usr/bin/env fish
set is_success true
function assert
# 省略...
end
echo "=== START TESTS ==="
# 省略...
echo "=== END TESTS ==="
+ if $is_success
+ echo ""
+ echo "All tests passed!"
+ exit 0
+ else
+ echo ""
+ echo "Some tests failed!"
+ exit 1
+ end
これで、実際にテストをするためのスクリプトが完成した。
# テスト用スクリプトに実行権限を付与する
$ chmod +x test/helloworld.fish
# 実際にテストを実行する
$ ./test/helloworld.fish
=== START TESTS ===
SUCCESS: expected 'Hello, World!', got 'Hello, World!'
SUCCESS: expected 'Usage: helloworld\n\nOptions:\n -h, --help Show this help message and exit', got 'Usage: helloworld\n\nOptions:\n -h, --help Show this help message and exit'
SUCCESS: expected 'Usage: helloworld\n\nOptions:\n -h, --help Show this help message and exit', got 'Usage: helloworld\n\nOptions:\n -h, --help Show this help message and exit'
=== END TESTS ===
All tests passed!
GitHub Actionsでテストが実行されるようにする
.gihub/workflows
ディレクトリを作り、その中にテストを実行するtest.yml
を作成する。このアクションでは、先ほど作ったテスト用のシェルスクリプトが自動実行され、テストに失敗した場合にはアクションが失敗します。
name: test
run-name: test
on: [push]
jobs:
run-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install fish shell
run: |
sudo apt-add-repository --yes --no-update ppa:fish-shell/release-3
sudo apt-get update -qq
sudo apt-get install --yes fish
shell: bash
- name: Install fisher and install `helloworld` plugin
run: |
curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher
fisher install ./
shell: fish {0}
- name: Run tests
run: |
chmod +x ./test/test.fish
./test/test.fish
shell: fish {0}
また余談ですが、ローカルでgithub actionsを実行したい場合には、actが便利なのでおすすめです。
fisher でインストールしてみる
今まではローカルディレクトリから作ったコマンド(プラグイン)をインストールしていましたが、これでは他のPCからインストールする際などには不便です。なので、この章ではgithubのリポジトリからインストールしてみましょう。
まずは、リモートリポジトリに今までの変更をpushしましょう(もうすでにしている方は必要ありません)。
gh repo create --public helloworld.fish
git add .
git commit -m 'first'
git remote add origin https://github.com/repos/YOURNAME/helloworld.fish
git push origin main
これで準備は整ったので、今までローカルディレクりからインストールしていたhelloworldコマンドを削除しましょう。
fisher remove /your/path/to/helloworld.fish
そして、GitHubのリポジトリからhelloworldコマンドをインストールしましょう。fisher install <GitHubの名前>/<リポジトリ名>
でインストールできます。
fisher install YOURNAME/helloworld.fish
おわりに
今回は、生産性がなく単に「Hello, World!」を表示するだけのコマンドを作成しました。しかし、この記事によって、fish shellでコマンド(プラグイン)を作成する手順を掴めていただけたら幸いです。
Discussion