Momozowy

fish は f riendly i nteractive shell の略であり、自ら親しみやすいシェルだと主張している。
なんて言ったって fish である。誰かの名前だったり、宗教色があったりしない。 かわいいお魚さんである。断然親しみやすい。
https://fishshell.com/docs/current/index.html#introduction
ほら、もう fish と友達になった気がするだろう?
ひとまず、次の画像を見てほしい。インストールした直後、何の設定もしていない fish の動作イメージだ。
実行可能なコマンドは青、オプションは水色、存在しないコマンドは赤など、すでに着色されている。
さらに ec
と入力した段階で、echo コマンドが提案されているのがわかるだろうか。
このように、fish は 24bit のトゥルーカラーをサポートし、強力な自動補完機能がインストール直後から使用できる。 追加の設定やプラグインのインストールは必要ない。
ほら、もう fish に興味が湧いてきたんじゃあないか?
fish の魅力は、なんと言っても強力な補完機能だ。
a
と入力すると、頭文字 a の実行可能なコマンドの候補が一つ表示される。昇順にソートされたコマンドリストの先頭のものだろう。
この時、右矢印
や C-f
, C-e
などで候補が確定された状態、つまりコマンド名が入力された状態になり、Enter キー
を押せば実行される。
また、TAB キー
により頭文字 a の実行可能なコマンド名が一覧できる。
ap
と入力した状態では、もちろん ap で始まるコマンド名が補完される。
さらには、各コマンドのサブコマンドやオプションも補完される。
例えば、apt
(スペース)と入力した状態で TAB キー
を押すと apt コマンドのサブコマンドが、
apt -
と入力した状態では apt コマンドのオプションが表示される。
これらは、fish が man ページをパースし生成したものだ。
ほら、もう fish の補完機能のとりこになっただろう?
fish はコマンド履歴も参照し、補完を行う。
「以前実行した時と同じオプションであのコマンドを実行したいなぁ」と思ったことはないだろうか。 そのオプションが複数あり、何と何を指定したのかをあまり思い出せなかったり、 スニペットとして記録しておけば良かったと、後悔したことはないだろうか。
fish ならそんな悩みとは無縁だ。コマンド履歴から補完してくれる。
次の画像では、sudo d
と入力した状態で、以前実行した sudo docker run ... が候補として提案されている。
別にコマンドじゃあなくてもいい。オプションの一部を入力し、上矢印
や C-p
を入力しても同様に、コマンド履歴を検索して補完してくれる。
次の画像では、 gatsby
と 上矢印
を入力した状態で、以前実行した sudo docker run ... が候補として提案されている。
ほら、もうバックグラウンドで fish をインストールしているんだろう?
私は fish のカスタマイズ性を気に入っている。ほとんどが関数として実現されているので、関数を上書きすることでカスタマイズができる。
後述するように、プロンプでさえ、プロンプトを表示するための関数を上書きすればいい。自由度は無限だ。
プロンプトを表示するたびに fortune
コマンドを実行すこともできるが、鬱陶しいのでお勧めはしない。
そしてその関数を定義するスクリプト構文も、正気を保った人が設計をしていることが容易に想像できる。if 文もfor文も、最後は end だ。一貫性がある。 esac は何の頭文字なのか、検討もつかない。
すでに zsh やその他のシェルをカスタマイズし不自由なく使用している人には、あまり魅力的ではないかもしれない。 bash や zsh をほぼカスタマイズせずそのまま使っていて、もっと便利にしたいと考えている、 しかし複雑な設定をすることや豊富なプラグインの中からベストなものをインストールすることについて二の足を踏んでいる、 という人には、ちょうどいい選択肢ではないだろうか。
非常に素晴らしいシェルだと思うのだが、GPL v2 をベースとしたライセンスの下に開発されているので、macOS の標準シェルに採用されることはなさそうだ。 代わりに、MIT ライセンスの zsh が採用された。その上、ソフトウェアデザイン 2020年7月号に「zsh <超> 入門」なる特集まで組まれている。 ますます zsh の人口は増えるところだろうが、私は fish は推したい。ここでは、その特集を意識しつつ fish の紹介を行いたいと思う。
macOS なら
brew install fish
や
sudo port install fish
Ubuntu なら
sudo apt-add-repository ppa:fish-shell/release-3
sudo apt-get update
sudo apt-get install fish
など、各種パッケージマネージャを使用してインストールすることが可能である。
fish の設定ファイルは下記の7種類があり、上から順に読み込まれ、重複する項目は上書きされていく。
$__fish_data_dir/config.fish
$__fish_config_dir/conf.d/*.fish
$__fish_sysconf_dir/conf.d/*.fish
/usr/share/fish/vendor_conf.d/*.fish
/usr/local/share/fish/vendor_conf.d/*.fish
$__fish_sysconf_dir/config.fish
$__fish_config_dir/config.fish
1 は、fish インストール時に作成されるもので、ユーザが編集するべきでない。 2 および 7 はユーザ個人の、3 および 6 はシステム全体で共有する設定ファイルである。 4 および 5 は、サードパーティのツールが参照する設定ファイルとなる。
2, 3, 4, 5 に該当するファイルは、フォルダごとに名前順に読み込まれ、 異なるフォルダに同名のファイルがあった場合、最初のファイルのみ実行される。
例えば次の4つのファイルがあった時、4 -> 3-> 2 の順に実行され、1 は実行されない。
$__fish_sysconf_dir/conf.d/A.fish
$__fish_sysconf_dir/conf.d/C.fish
$__fish_config_dir/conf.d/B.fish
$__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" つきでそうお願いされる。
ちょっと距離をおかれた?
fish の書式は bash や zsh と幾分異なる。式や文ごとにセミコロンまたは改行で区切り、
構文の最後は end
で終わらなければらない。この end
は省略することができない。
また、スコープを区切るときなどに {}
で囲ったりするが、これは begin
と end
で実現される。
begin
[COMMANDS...;]
end
ちょっと調べただけでは、catch
や throw
などの構文を見つけることができなかった。
fish では次のようにして関数を定義する。
function NAME [OPTIONS]; BODY; end
https://fishshell.com/docs/current/cmds/function.html
関数定義時に、複数の名前を指定することはできない。
しかし、 functions
コマンドの c
オプションにて関数をコピーすることができる。
functions -c OLDNAME NEWNAME
先述の begin
コマンドによって、スコープが区切られ即時実行されると言う zsh の無名関数と同等の挙動が実現できる。
関数のモジュール化は難しいかもしれない。fish にも、関数を定義しておくファイルが設定ファイルとは別に用意されており、
$fish_function_path
ディレクトリ内の接尾語が .fish
のファイルがそれだ。
しかし、zsh が autoload
コマンドにて関数を読み込むのに対し、fish では変更も即時反映される。
イベントをトリガーにして、関数を実行させることができる。
表1 および表2の通り、イベントに応じて、特定の名前またはオプションをもつ関数を function
コマンドにて定義すれば良い。
イベント | オプション |
---|---|
シグナル | -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 |
イベント | 関数名 |
---|---|
プロンプトの表示 | 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
bash と同様、シグナルに割り込む trap
コマンドが、 fish にも存在する。
しかし、これは POSIX 準拠なシェルへの後方互換性のために存在しているのみで、
先述の function
コマンド、-s SIGSPEC
オプションでのフックが推奨される。
https://fishshell.com/docs/current/cmds/trap.html
bash と同様、alias
コマンドが存在する。
より複雑な定義をしたい場合は、function
コマンドにて関数を定義してエイリアスとする。
残念ながら、zsh のように、パイプやリダイレクトから始まるものや、サフィックスエイリアスは実現できない。
そんなものはないよ。
また、zsh における $NULLCMD
や $READNULLCMD
と同様の変数も存在しない。
jobs コマンドの実行結果として出力されないようプロセスを起動するには、disown
コマンドを用いる。
https://fishshell.com/docs/current/cmds/disown.html
fish では、$((EXPRESSION))
のように unfriendly な構文でなく、math
コマンドにて計算を行う。
しかし、10進数の16進数への変換や排他的論理和はない。小数点演算は当たり前にできる。
面白いのは掛け算で、その演算子は x
である。馴染みの深い *
を使うこともできるが、
""
で囲んでやる必要がある。何故なら、*
はファイル名やフォルダ名に展開されてしまうから。
条件式を評価するコマンドとして、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
ワイルドカード、ブレース展開、変数展開は当たり前のようにできる。 しかし、展開時に一致するものを取り除いたり、置換したりする機能は実装されていない。
配列の添え字は、fish や zsh は 1 オリジン、bash は 0 オリジンとなる。
配列の最後は -1
、範囲は 2..4
、逆順は -1..1
や 4..2
と指定する。
引数全体は$argv
という配列に格納されており、bash などでお馴染みの $1
$2
といった変数は存在しない。
また終了値は $?
の代わりに $status
、パイプラインに含まれる各コマンドの終了値は $pipestatus
に格納される。
$PWD
や $IFS
などの変数は存在しており、現在設定されている変数は set
コマンドにて確認できる。
引数なしで実行すると、ホームディレクトリへ移動する。
相対パスを指定した場合、$CDPATH
配列のディレクトリ直下を探し、
見つからなければカレントディレクトリの直下を探して移動する。
cd
コマンドを使用しなくとも、 .
や /
, ~
で始まる、または /
で終わる文字列を
入力した場合、fish はディレクトリと解釈し移動を試みる。
引き数として -
を指定することで、直前にいたディレクトリへ移動できる。
また、prevd
, nextd
コマンドで cd履歴を辿ることも可能だ。
cd履歴は、 cdh
コマンドで確認できる。
例えば、ディレクトリを A -> B -> C と遷移した時、cd -
を繰り返し実行するとディレクトリ B と C を行き来する。
また、prevd
を1回実行するとディレクトリBに、もう一度実行するとディレクトリAに戻る。
ここで nextd
を実行すると、ディレクトリ B へ進む。
pushd
コマンドでは、cd
コマンドと同様に指定したディレクトリへ移動する。
同時に、移動先のディレクトリがディレクトリスタックへ保存される。
popd
コマンドによって、ディレクトリスタックの先頭から一つ取り出し移動できる。
dirs
コマンドでは、ディレクトリスタックを表示し、任意のディレクトリを指定し移動できる。
*
は、カレントディレクトリ内のファイル名に展開される。
**
は、カレントディレクトリ以下、サブディレクトリ内のファイル名まで再起的に展開される。
bash/zsh と同様、Emacs モードと vi モードがある。
デフォルトは Emacs モードで、fish_vi_key_bindings
コマンドにより vi モードに、
さらに fish_default_key_bindings
コマンドにより Emacs モードに切り替えることができる。
fish の help
コマンドは、対象のコマンドのヘルプをブラウザで開く。
また、ほとんどのビルトインコマンドに --help
オプションがあり、端末上にヘルプを表示することができる。
fish_config
コマンドを実行してみてほしい。
Web ページが開き、いくつかのテーマやプロンプトが選べるようになっている。
気に入ったものがあれば、"Set Theme" ボタンを押して反映させれば良い。
表示されたテーマをベースに若干のカスタマイズをしたい時、 "Customize" ボタンからできるようだが、カラーパレットに予め定義された色しか選択できない。
しかし心配しないでほしい。まず "Set Theme" ボタンを押しベースとなるテーマを反映させる。 この時、ページ下部、いくつかのテーマが並んでいるうちの一番左上にある "Current" にも、選択したテーマが反映されている。 この "Current" を選択して "Customize" しよう。 先ほどとは違い、様々な色を選択できるようになっている。
もっと自由にカスタマイズしたい時は、設定ファイルを作成しよう。 大丈夫。簡単だ。これまで見てもらった通り、構文が分かりやすい。
先述の通り、個人用のカスタマイズは ~/.config/fish/config.fish
に記述すれば良いだろう。
プロンプトの設定は、fish_prompt
関数を定義することで行う。
~/.config/fish/config.fish
に記述してもいいし、
関数なので ~/.config/fish/functions/fish_prompt.fish
というファイルを作成してもいいだろう。
fish_prompt
関数の中で出力した文字列が、プロンプトとして表示される。
プロンプト定義のために \u
や \e[36m
などという訳のわからない構文を覚える必要はなく、
whoami
や set_color cyan
などの friendly
な、
みんな知っているあのコマンドやそのコマンドを駆使してプロンプトを自由に定義できる。
https://fishshell.com/docs/current/cmds/fish_prompt.html
個人開発のプロンプト、テーマ、関数が公開されている。 これらを管理するツールとして、
などがある。ここでは、いずれも解説はしない 私は fish 標準の機能で満足してしており、どれも使ってことがないので、むしろできない。
やはり zsh は人気なのだろう。シェルの話になると、bash から zsh に乗り換えるものばかりだ。 そんな中に fish を紹介する記事を投じることで、シェルの選択肢を増やしたい。
私は zsh が分からぬ。初めて出会ったシェルは bash だった。 bash に不満を覚え、乗り換えたのが fish だった。 もちろん zsh も候補にはあったのだが、遅いか何とかと言う評判を聞いて fish に決めた。 この記事を書くにあたって zsh についても少し調べてみたのだが、 確かに zsh は高機能だ。おそらく、痒いところにも手が届くのだろう。 しかし、fish で出来なくとも"他のコマンドで"同様のことが実現できれば、 それで充分ではないだろうか。シェルで全てを実現する必要はないだろう。 これは、fish の設計思想の一つでもある。