fish <超>入門
Posted: 4 years ago

fish は f riendly i nteractive shell の略であり、自ら親しみやすいシェルだと主張している。

なんて言ったって fish である。誰かの名前だったり、宗教色があったりしない。 かわいいお魚さんである。断然親しみやすい。

fish shell logo

https://fishshell.com/docs/current/index.html#introduction

ほら、もう fish と友達になった気がするだろう?

すぐ使える fish

ひとまず、次の画像を見てほしい。インストールした直後、何の設定もしていない fish の動作イメージだ。

インストール直後のfish

実行可能なコマンドは青、オプションは水色、存在しないコマンドは赤など、すでに着色されている。 さらに ec と入力した段階で、echo コマンドが提案されているのがわかるだろうか。

このように、fish は 24bit のトゥルーカラーをサポートし、強力な自動補完機能がインストール直後から使用できる。 追加の設定やプラグインのインストールは必要ない。

ほら、もう fish に興味が湧いてきたんじゃあないか?

自動補完

fish の魅力は、なんと言っても強力な補完機能だ。

a と入力すると、頭文字 a の実行可能なコマンドの候補が一つ表示される。昇順にソートされたコマンドリストの先頭のものだろう。

suggest a2p

この時、右矢印C-f, C-e などで候補が確定された状態、つまりコマンド名が入力された状態になり、Enter キー を押せば実行される。 また、TAB キー により頭文字 a の実行可能なコマンド名が一覧できる。

test

ap と入力した状態では、もちろん ap で始まるコマンド名が補完される。

さらには、各コマンドのサブコマンドやオプションも補完される。 例えば、apt (スペース)と入力した状態で TAB キー を押すと apt コマンドのサブコマンドが、 apt - と入力した状態では apt コマンドのオプションが表示される。 これらは、fish が man ページをパースし生成したものだ。

apt subcommandlist

apt optionlist

ほら、もう fish の補完機能のとりこになっただろう?

コマンド履歴

fish はコマンド履歴も参照し、補完を行う。

「以前実行した時と同じオプションであのコマンドを実行したいなぁ」と思ったことはないだろうか。 そのオプションが複数あり、何と何を指定したのかをあまり思い出せなかったり、 スニペットとして記録しておけば良かったと、後悔したことはないだろうか。

fish ならそんな悩みとは無縁だ。コマンド履歴から補完してくれる。 次の画像では、sudo d と入力した状態で、以前実行した sudo docker run ... が候補として提案されている。

history suggestion command

別にコマンドじゃあなくてもいい。オプションの一部を入力し、上矢印C-p を入力しても同様に、コマンド履歴を検索して補完してくれる。 次の画像では、 gatsby上矢印 を入力した状態で、以前実行した sudo docker run ... が候補として提案されている。

history suggestion string

ほら、もうバックグラウンドで fish をインストールしているんだろう?

他のシェルと比較して

私は fish のカスタマイズ性を気に入っている。ほとんどが関数として実現されているので、関数を上書きすることでカスタマイズができる。 後述するように、プロンプでさえ、プロンプトを表示するための関数を上書きすればいい。自由度は無限だ。 プロンプトを表示するたびに fortune コマンドを実行すこともできるが、鬱陶しいのでお勧めはしない。

そしてその関数を定義するスクリプト構文も、正気を保った人が設計をしていることが容易に想像できる。if 文もfor文も、最後は end だ。一貫性がある。 esac は何の頭文字なのか、検討もつかない。

すでに zsh やその他のシェルをカスタマイズし不自由なく使用している人には、あまり魅力的ではないかもしれない。 bash や zsh をほぼカスタマイズせずそのまま使っていて、もっと便利にしたいと考えている、 しかし複雑な設定をすることや豊富なプラグインの中からベストなものをインストールすることについて二の足を踏んでいる、 という人には、ちょうどいい選択肢ではないだろうか。

非常に素晴らしいシェルだと思うのだが、GPL v2 をベースとしたライセンスの下に開発されているので、macOS の標準シェルに採用されることはなさそうだ。 代わりに、MIT ライセンスの zsh が採用された。その上、ソフトウェアデザイン 2020年7月号に「zsh <超> 入門」なる特集まで組まれている。 ますます zsh の人口は増えるところだろうが、私は fish は推したい。ここでは、その特集を意識しつつ fish の紹介を行いたいと思う。

インストール

macOS なら brew install fishsudo port install fish

Ubuntu なら

sudo apt-add-repository ppa:fish-shell/release-3
sudo apt-get update
sudo apt-get install fish

など、各種パッケージマネージャを使用してインストールすることが可能である。

https://fishshell.com

設定ファイル

fish の設定ファイルは下記の7種類があり、上から順に読み込まれ、重複する項目は上書きされていく。

  1. $__fish_data_dir/config.fish
  2. $__fish_config_dir/conf.d/*.fish
  3. $__fish_sysconf_dir/conf.d/*.fish
  4. /usr/share/fish/vendor_conf.d/*.fish
  5. /usr/local/share/fish/vendor_conf.d/*.fish
  6. $__fish_sysconf_dir/config.fish
  7. $__fish_config_dir/config.fish

1 は、fish インストール時に作成されるもので、ユーザが編集するべきでない。 2 および 7 はユーザ個人の、3 および 6 はシステム全体で共有する設定ファイルである。 4 および 5 は、サードパーティのツールが参照する設定ファイルとなる。

2, 3, 4, 5 に該当するファイルは、フォルダごとに名前順に読み込まれ、 異なるフォルダに同名のファイルがあった場合、最初のファイルのみ実行される。

例えば次の4つのファイルがあった時、4 -> 3-> 2 の順に実行され、1 は実行されない。

  1. $__fish_sysconf_dir/conf.d/A.fish
  2. $__fish_sysconf_dir/conf.d/C.fish
  3. $__fish_config_dir/conf.d/B.fish
  4. $__fish_config_dir/conf.d/A.fish

ユーザ個人の設定は、混乱を避けるため全て 7 に書いてしまっても良い。 $__fish_config_dir は、デフォルトで ~/.config/fish である。 最後に必ず実行されるため、それ以外の設定ファイルによる設定を上書きでき、非常にわかりやすくなる。

ログインシェルとインタラクティブシェルで処理を分けたい場合、 コマンド status --is-login または status --is-interactive の結果で条件分岐し実現する。 次の例は、ログインシェルの開始時に実行され、インタラクティブシェルの開始時には実行されない。

if status --is-login
    set -x PATH $PATH ~/linux/bin
end

bash や zsh とは異なり、設定ファイルに対して、ログインシェルで実行されるもの、 インタラクティブシェルで実行されるものと言った分類は存在しない。

https://fishshell.com/docs/current/index.html#initialization-files

変数定義

fish では、set コマンドを使用して変数の定義を行う。 bash/zsh のように = を使おうとすると、"please" つきでそうお願いされる。 ちょっと距離をおかれた?

test

制御構文

fish の書式は bash や zsh と幾分異なる。式や文ごとにセミコロンまたは改行で区切り、 構文の最後は end で終わらなければらない。この end は省略することができない。

control syntax

また、スコープを区切るときなどに {} で囲ったりするが、これは beginend で実現される。

begin
    [COMMANDS...;]
end

例外処理

ちょっと調べただけでは、catchthrow などの構文を見つけることができなかった。

関数定義

fish では次のようにして関数を定義する。

function NAME [OPTIONS]; BODY; end

https://fishshell.com/docs/current/cmds/function.html

複数名関数

関数定義時に、複数の名前を指定することはできない。 しかし、 functions コマンドの c オプションにて関数をコピーすることができる。

functions -c OLDNAME NEWNAME

無名関数

先述の begin コマンドによって、スコープが区切られ即時実行されると言う zsh の無名関数と同等の挙動が実現できる。

noname function

関数の自動読み込み

関数のモジュール化は難しいかもしれない。fish にも、関数を定義しておくファイルが設定ファイルとは別に用意されており、 $fish_function_path ディレクトリ内の接尾語が .fish のファイルがそれだ。 しかし、zsh が autoload コマンドにて関数を読み込むのに対し、fish では変更も即時反映される。

フック

イベントをトリガーにして、関数を実行させることができる。 表1 および表2の通り、イベントに応じて、特定の名前またはオプションをもつ関数を function コマンドにて定義すれば良い。

表1. オプションを指定することでフックできるイベント
イベント オプション
シグナル -s or --on-signal SIGSPEC
ユーザ定義イベント -e or --on-event EVENT_NAME
変数の変更 -v or --on-variable VARIABLE_NAME
job の終了 -j or --on-job-exit PGID
process の終了 -p or --on-process-exit PID
表2. 特定の関数名でフックできるイベント
イベント 関数名
プロンプトの表示 fish_prompt
コマンドが見つからなかった時 fish_command_not_found
コマンドを実行する直前 fish_preexec
コマンドの実行に失敗した直後 fish_postexec
コマンドのキャンセル  fish_cancel
fish の終了 fish_exit

https://fishshell.com/docs/current/index.html#event https://fishshell.com/docs/current/cmds/function.html

trap コマンド

bash と同様、シグナルに割り込む trap コマンドが、 fish にも存在する。 しかし、これは POSIX 準拠なシェルへの後方互換性のために存在しているのみで、 先述の function コマンド、-s SIGSPEC オプションでのフックが推奨される。

https://fishshell.com/docs/current/cmds/trap.html

エイリアス定義

bash と同様、alias コマンドが存在する。 より複雑な定義をしたい場合は、function コマンドにて関数を定義してエイリアスとする。

残念ながら、zsh のように、パイプやリダイレクトから始まるものや、サフィックスエイリアスは実現できない。

マルチリダイレクト

そんなものはないよ。

また、zsh における $NULLCMD$READNULLCMD と同様の変数も存在しない。

job に関する仕様

jobs コマンドの実行結果として出力されないようプロセスを起動するには、disown コマンドを用いる。

disown

https://fishshell.com/docs/current/cmds/disown.html

算術式と演算子

fish では、$((EXPRESSION)) のように unfriendly な構文でなく、math コマンドにて計算を行う。

しかし、10進数の16進数への変換や排他的論理和はない。小数点演算は当たり前にできる。 面白いのは掛け算で、その演算子は x である。馴染みの深い * を使うこともできるが、 "" で囲んでやる必要がある。何故なら、* はファイル名やフォルダ名に展開されてしまうから。

math

条件式

条件式を評価するコマンドとして、POSIX 準拠の test EXPRESSION[ EXPRESSION ] がビルトインコマンドとして存在するが、 [[ EXPRESSION ]] は存在しない。

文字列比較を行いたいときは、string コマンドの match サブコマンドを使用する方がいいだろう。 -r, --regex オプションを指定することで、Perl 互換の正規表現を使用することができる。

https://fishshell.com/docs/current/cmds/test.html https://fishshell.com/docs/current/cmds/string.html

プロンプトの設定

fish では、fish_prompt 関数を実行することで、プロンプトを表示している。 もちろん自由に関数を定義できるので、プロンプトを表示するたびに fortune コマンドを実行させることもできるが、 非常に鬱陶しい。

function fish_prompt
    printf "\n"
    fortune
    printf "\n\n\$ "
end

https://fishshell.com/docs/current/cmds/function.html

コマンド履歴

fish のコマンド履歴は非常に便利だ。自動補完の情報元にもなるし、検索もできる。

プロンプトが表示された後、上キーC-p を押すと、bash/zsh と同様にコマンド履歴のうち新しいものから順に表示される。 何かしらの文字を入力した状態で 上キーC-p を押すと、fish はその文字を含むものをコマンド履歴から検索して表示してくれるのだ。

例えば、docker run コマンドはポートやマウントポイントなどを指定すると、途端に長くなってしまう。 docker-compose.yml を書くほど大仰ではないし、書くのも面倒だ。書いたとして、実行のたびに目的のファイルを見つけるのも手間がかかる。 そんな時はただコマンドを実行して、忘れてしまえばいい。fish が覚えていてくれる。 同じコマンドを実行したくなったら docker run と入力し、おもむろに 上キー を押せばいい。

逆に、覚えていて欲しくないコマンドもあるだろう。間違って実行した履歴が出てきても邪魔だ。 そんな時は history delete "search string" コマンドで消してしまえばいい。

プロセス置換

psub コマンドにて、プロセス置換を行う。 細かい説明はないが、サブシェルでの実行内容を psub コマンドに渡して復帰することで、 プロセス置換を行っているように見える。

diff (sort a.txt | psub) (sort b.txt | psub)

bash/zsh のように、出力先として指定することはできなかった。

https://fishshell.com/docs/current/cmds/psub.html

パラメータ展開

ワイルドカード、ブレース展開、変数展開は当たり前のようにできる。 しかし、展開時に一致するものを取り除いたり、置換したりする機能は実装されていない。

expand

配列の仕様

配列の添え字は、fish や zsh は 1 オリジン、bash は 0 オリジンとなる。

配列の最後は -1、範囲は 2..4、逆順は -1..14..2 と指定する。

array

シェル変数

引数全体は$argv という配列に格納されており、bash などでお馴染みの $1 $2 といった変数は存在しない。 また終了値は $? の代わりに $status、パイプラインに含まれる各コマンドの終了値は $pipestatus に格納される。

$PWD$IFS などの変数は存在しており、現在設定されている変数は set コマンドにて確認できる。

cd コマンドの挙動とディレクトリスタック

cd コマンド

引数なしで実行すると、ホームディレクトリへ移動する。

相対パスを指定した場合、$CDPATH 配列のディレクトリ直下を探し、 見つからなければカレントディレクトリの直下を探して移動する。

cd コマンドを使用しなくとも、 ./, ~ で始まる、または / で終わる文字列を 入力した場合、fish はディレクトリと解釈し移動を試みる。

引き数として - を指定することで、直前にいたディレクトリへ移動できる。 また、prevd, nextd コマンドで cd履歴を辿ることも可能だ。 cd履歴は、 cdh コマンドで確認できる。

例えば、ディレクトリを A -> B -> C と遷移した時、cd - を繰り返し実行するとディレクトリ B と C を行き来する。 また、prevd を1回実行するとディレクトリBに、もう一度実行するとディレクトリAに戻る。 ここで nextd を実行すると、ディレクトリ B へ進む。

ディレクトリスタック

pushd コマンドでは、cd コマンドと同様に指定したディレクトリへ移動する。 同時に、移動先のディレクトリがディレクトリスタックへ保存される。

popd コマンドによって、ディレクトリスタックの先頭から一つ取り出し移動できる。 dirs コマンドでは、ディレクトリスタックを表示し、任意のディレクトリを指定し移動できる。

ファイル名補完

* は、カレントディレクトリ内のファイル名に展開される。 ** は、カレントディレクトリ以下、サブディレクトリ内のファイル名まで再起的に展開される。

complement file

コマンドラインエディタ

bash/zsh と同様、Emacs モードと vi モードがある。 デフォルトは Emacs モードで、fish_vi_key_bindings コマンドにより vi モードに、 さらに fish_default_key_bindings コマンドにより Emacs モードに切り替えることができる。

help コマンド

fish の help コマンドは、対象のコマンドのヘルプをブラウザで開く。

また、ほとんどのビルトインコマンドに --help オプションがあり、端末上にヘルプを表示することができる。

fish のカスタマイズ

Web での設定

fish_config コマンドを実行してみてほしい。 Web ページが開き、いくつかのテーマやプロンプトが選べるようになっている。 気に入ったものがあれば、"Set Theme" ボタンを押して反映させれば良い。

fish config

表示されたテーマをベースに若干のカスタマイズをしたい時、 "Customize" ボタンからできるようだが、カラーパレットに予め定義された色しか選択できない。

customize theme

しかし心配しないでほしい。まず "Set Theme" ボタンを押しベースとなるテーマを反映させる。 この時、ページ下部、いくつかのテーマが並んでいるうちの一番左上にある "Current" にも、選択したテーマが反映されている。 この "Current" を選択して "Customize" しよう。 先ほどとは違い、様々な色を選択できるようになっている。

customize current

もっと自由にカスタマイズしたい時は、設定ファイルを作成しよう。 大丈夫。簡単だ。これまで見てもらった通り、構文が分かりやすい。

設定ファイル

先述の通り、個人用のカスタマイズは ~/.config/fish/config.fish に記述すれば良いだろう。

プロンプト

プロンプトの設定は、fish_prompt 関数を定義することで行う。

~/.config/fish/config.fish に記述してもいいし、 関数なので ~/.config/fish/functions/fish_prompt.fish というファイルを作成してもいいだろう。

fish_prompt 関数の中で出力した文字列が、プロンプトとして表示される。 プロンプト定義のために \u\e[36m などという訳のわからない構文を覚える必要はなく、 whoamiset_color cyan などの friendly な、 みんな知っているあのコマンドやそのコマンドを駆使してプロンプトを自由に定義できる。

https://fishshell.com/docs/current/cmds/fish_prompt.html

外部ツール

個人開発のプロンプト、テーマ、関数が公開されている。 これらを管理するツールとして、

  • Fisher
  • Fundle
  • Oh My Fish

などがある。ここでは、いずれも解説はしない 私は fish 標準の機能で満足してしており、どれも使ってことがないので、むしろできない。

余談

やはり zsh は人気なのだろう。シェルの話になると、bash から zsh に乗り換えるものばかりだ。 そんな中に fish を紹介する記事を投じることで、シェルの選択肢を増やしたい。

私は zsh が分からぬ。初めて出会ったシェルは bash だった。 bash に不満を覚え、乗り換えたのが fish だった。 もちろん zsh も候補にはあったのだが、遅いか何とかと言う評判を聞いて fish に決めた。 この記事を書くにあたって zsh についても少し調べてみたのだが、 確かに zsh は高機能だ。おそらく、痒いところにも手が届くのだろう。 しかし、fish で出来なくとも"他のコマンドで"同様のことが実現できれば、 それで充分ではないだろうか。シェルで全てを実現する必要はないだろう。 これは、fish の設計思想の一つでもある。

https://fishshell.com/docs/current/design.html