🎏

MacOSでfishシェルの環境構築と関数について

に公開

はじめに

ずっと前から zsh シェルを使っているのですけど、活用することができるというわけではありません。簡単なコマンド以外、関数の書き方や PATH などがほとんど分かりませえんでした。仕事でWindowsがしか使えありませんが、そのおかげで徐々に PowerShell の長所を覚えててもらっただけでなく、シェルの重要性も教わりました。 fish は公式に豊かなチュートリアルがたくさんあるし、構文とかが初心者のわたしにしてもやさしそうです。しかもほかのframeworkやpluginに頼らずにsyntax highlightingとautosuggestions [1] をサポートしてよかった(バッテリーインクルード)。 fishを学んでいるあいだは、シェルの知識もどんどんもらえたとは思いませんでした。

対象読者

  • MacOSユーザーのみ
  • fishの構築が知りたい方
  • 初心者向け

環境構築

アプリケーション バージョン
OS macOS Tahoe 26.0.1 arm64
fish 4.1.0
pyenv 2.6.7
pyenv-virtualenv 1.2.4
zsh 5.9
WezTerm 20240203-110809-5046fc22
Python 3.13.3

わたしの環境構築

インストール

ターミナルや WezTerm などの端末に brew install fish を執行してfishをインストールします。

fish --version
which fish

pyenvに関する設定

pyenv [2] は簡単なPythonばバージョン管理であるアプリケーションが、仕組みが PATH を利用してPythonのバージョンに支配させるので、fishで使われるようにfishのconfigファイルを編集することが必要です。下記の段落がpyenvを利用している方だけでご参考までに。

# インタラクティモード

fish  # fishを執行する

# vimもしくはあなたの一番好きなテキストエディタ
cd $__fish_config_dir && vim config.fish

pyenvのGitHubページに従って、下記の設定を config.fish に加えて:

config.fish
if status is-interactive
  set -Ux PYENV_ROOT $HOME/.pyenv
  test -d $PYENV_ROOT/bin; and fish_add_path $PYENV_ROOT/bin

  # pyenv-virtualenvがないで、pyenvのみ場合
  pyenv init --path | source

  # pyenv/pyenv-virtualenvが使っている場合
  # fishに指定すれば起動時間がよく減らせるそうだ
  pyenv init - fish | source
  pyenv virtualenv - fish | source
end

GitHubページでの Advanced Configuration によると、 pyenv init --path はただshimsパズを用意してたとそのパズが PATH に第一順番を支えています (rehashing)。というのは、環境にとっては、追加設定は必要がどうかが違います。確認するために自ら端末に pyenv init --path そして pyenv init - fish を入力してみましょう。

他のPATH変数に関する設定

pyenvのパズを除いて、zshにもともと設定しあるパズがfishに加えることは必要があります。

# インタラクティモード

# しばらくfishからzshに戻る
exec zsh

echo $PATH | tr ':' '\n'

# fishに帰って
fish

# 両方の違いを比べて
echo $fish_user_paths

他のパズを加えたければ、fishに増やす方法は:

cd $__fish_config_dir; and nvim config.fish
config.fish
if status is-interactive
  # 例えばZenn CLIのパズ
  fish_add_path /Users/shinei/.nvm/versions/node/v22.14.0/bin

  # Homebrewのパズを二つ
  fish_add_path /opt/homebrew/bin /opt/homebrew/sbin
end

もしかしたら fish_user_paths を修正する場合は:

# インタラクティモード

# 設定したパスを調べて
echo $fish_user_paths

# 一番目のパズを取り去る
set --erase fish_user_paths[1]

aliasの設定

重大な PATH が設定してきたあとで、aliasを設定しましょう。fishの言葉では abbrabbreviation[3] が zshの alias と等しいです。ちなみにfishには Heredocs って構文が使用のようがないので [4] 、代わりに printf または echo が使われます。

# インタラクティモード

# zshでaliasを設定する方法
cat >> ~/.zshrc << EOF
alias rm='rm -i'
alias emacs='emacs -nw'
EOF
# fishでabbrを設定する方法
printf %s\n "abbr --add rm rm -i" "abbr --add emacs emacs -nw" >> ~/.config/fish/config.fish

abbr--set-cursor=MARKER ってオプションを利用すれば、短縮コマンドを入力してきたところで、カーソルが指定された位置に引っ越すことができます。

config.fish
# 単語をDictionaryで調べる
# デフォルト記号「%」が到着地点にする
abbr -a --set-cursor dict open dict://% -g

# より複雑なコマンドなら引用符で囲む
abbr -a --set-cursor gd 'git diff --word-diff-regex=. (git diff --name-only | sed -n %p)'

# 自定義の記号「msg」
abbr -a --set-cursor=msg gc git commit -m "'msg'"

EXPORT環境変数の設定

それぞれの構文が少し違います。zshは:

# インタラクティモード

# zshで環境変数を設定する方法
echo "export DEEPL_API_KEY='{あなたのAPIキー}'" >> ~/.zshrc

fishには:

# fishで環境変数を設定する方法
# --export or -x
echo 'set -x DEEPL_API_KEY {あなたのAPIキー}' >> ~/.config/fish/config.fish
echo 'set -x DJANGO_SECRET_KEY {あなたのAPIキー}' >> ~/.config/fish/config.fish

プロンプト

zshのprompt( PS1 )が覚えにくいと思っています。

~/.zshrc
PROMPT='%B%F{#20B2AA}%n%f%b %F{#FFF5EE}%3~%f %F{#87CEFA}%#%f '
RPROMPT='%F{#3CB371}%t%f'

その比べて、promptのシンタックスが見たところまっすぐなのじゃないでしょうか。自ら自定義のpromptがこちらです:

fish_prompt.fish
function fish_prompt
    set -l last_status $status
    set -l stat
    if test $last_status -ne 0
        set stat (set color red) "[$last_status]" (set_color normal)
    end

    string join '' -- \
    (set_color 00FFFF --bold) (whoami) ' ' (set_color normal)\
    (set_color F0FFFF) (prompt_pwd --full-length-dirs 3) ' ' (set_color normal)\
    (set_color F5F5F5) $stat (set_color normal) '> '
end

function fish_right_prompt
    echo -n $fish_bind_mode (fish_git_prompt)\
    (set_color 87CEEB; date '+%H:%M:%S'; set_color normal)
end

fish_prompt.fish のディレクトリは ~/.config/fish の中に:

cd ~/.config/fish
mkdir functions; and cd functions  # フォルダーを作成する

touch fish_prompt.fish  # ファイルを作成する
: .
: ├── config.fish
: └── functions
:     └── fish_prompt.fish

詳しくはぜひ公式の実演 [5] をご覧ください。

挨拶のメッセージ

挨拶のメッセージ [6] は設定された変数 fish_greeting に扱わせます。外したいなら ~/.config/fish/config.fishset -g fish_greeting を書き入れます。もちろん自定義してもOKです。

config.fish
function fish_greeting
  echo やさしいインタラクティブシェルであるfish へようこそ
  echo fish の使い方は (set_color green)help(set_color normal) と入力してください
  echo (set_color yellow)(date "+%d %B, %y")(set_color normal)
end

端末にデフォルトシェルを変える

もしWezTerm [7] を使っているなら、fishをデフォルトシェルとして指定できます。 ~/.wezterm.lua に:

~/.wezterm.lua
local wezterm = require("wezterm")
local config = wezterm.config_builder()

config.default_prog = { "/opt/homebrew/bin/fish", "-l" }

return config

システム的にデフォルトシェルにする

公式からの引用文:

Warning : Setting fish as your login shell may cause issues, such as an incorrect $PATH . Some operating systems, including a number of Linux distributions, require the login shell to be Bourne-compatible and to read configuration from /etc/profile . fish may not be suitable as a login shell on these systems.

どのような影響があるかがずいぶん理解してもらうまで、なるべくデフォルトシェルを変更しないほうがいいと思います。下記の変更する方法がご参考までに:

# インタラクティモード

# 現在デフォルトシェル
which $SHELL

# fishのパスをシェルリストに書き込む
# ルート権限が必要だ
echo '/opt/homebrew/bin/fish' | sudo tee -a /etc/shells
cat /etc/shells

chsh -s /opt/homebrew/bin/fish

仮想環境

大体他のシェルと同じです。

# インタラクティモード

# Pythonビルトインモジュールvenv
python -m venv myproject

# 必ず語尾に.fishを加えて
source myproject/bin/activate.fish

# pyenv/pyenv-virtualenvにはいつも通り
pyenv virtualenv myproject
pyenv activate myproject

関数

関数が簡単であるにしても、様々なタスクに役立てると思っています。例えば、プログラムがいつでも仮想環境に覆い被さる関数:

# prompt-toolkit/ptpython
# A better Python REPL
function ptpython
  pyenv activate ptpython
  command ptpython
  pyenv deactivate
end

詳しくは公式のマニュアル [9] または man function に参考してください。わずかですが、以下の関数を例示としていくつかの不可欠な構文を挙げます。

引数ハンドリング

リスト変数 argv [10] を引数として二つ以上の引数が渡せます。

cd $fish_function_path[1] && pwd

# 次の例のファイルを作成する
touch weblio.fish
weblio.fish
function weblio -d 'ブラウザを開いて単語をWeblioウェブサイトに調べる'
    if test (count $argv) -gt 0
        for i in (seq (count $argv))
            set -a url (string join / 'https://www.weblio.jp/content' $argv[$i])
        end
    else
        set -f url 'https://www.weblio.jp'
    end
    command open -u $url -g
end

執行してみて:

weblio '一つ' '二つ' '三つ'

オプション

次はオプションについての argparse をご紹介します。名はPythonの argparse [11] と同じだったけど、二つとも全く関係なく、ただお互いに役割はよく似ています。簡単的に言うと argparse [12] はいろんなオプションを組み立てることができます。以下は前回の記事「Macの辞書アプリとObsidianをPythonで連携しましょう」の中に自作のPythonスクリプト mac_dict_app.py を具体例として argparse を説明します。

https://zenn.dev/shinei/articles/06a9b9ec558bde

元のPythonスクリプト

このプログラムはオプションが二つ --help--output です。そのうちの --output は選択肢のオプション( --output default--output less )であるのですけど、対処する方法が選択肢なしのオプションと少し違います。

# 擬似コード

function 関数名
    if 引数がゼロを超える  # if test (count $argv) -gt 0
  argparse 'h/help' 'o/option' -- $argv

  # 関数名 --help
  if set -ql _flag_h  # -ql が --query --local と等しい
      helpページを標準出力(stdout)に示す  # echo 'helpページ'
      早期リターン  # return 0
  else if オプション「o」がある  # set -ql _flag_o
      そのオプションの働き
  else
      return 1  # return false
  end
    else
  # 引数のないで、関数名のまま執行する場合
  他の働きがあったら
    end
end

まず ~/.config/fish/functions に関数ファイルを作成します( cd $fish_function_path[1]; and touch dict_query.fish )。プログラムでのオプションを元にそのままfish関数に作ります。

dict_query.fish
function dict_query
  set -l script_path ~/Documents/Snippets/Python/mac_dict_app.py

  if test (count $argv) -gt 0
    argparse -x h,o 'h/help' 'o/output=!string match -rq "default|less" "$_flag_value"' -- $argv
    or return

    if set -ql _flag_help
      command python $script_path --help
    else if set -ql _flag_output
      command python $script_path $argv --output $_flag_output
    else if string match -qv -- '-*' $argv  # --quiet --invert
      # デフォルト
      command python $script_path $argv
    end
  else
    echo '引数とオプションがなければ、何かやることがあるか'
    return 1
  end
end

argparse-x h,o というのはそのグループでのオプションが同時に指定させることを控えます。例えば:

dict_query --help --output less

エラーメッセージ(stderr):

dict_query: h/help o/output: options cannot be used together

基本的に argparse -x h,o 'h/help' 'o/option' もずいぶんですが、先行検査するため、バリデーション [13] の構文を加えたほうがいいと思います。

ここまでPython関数 mac_dict_app.py 元々のオプションを dict_query.fish に全て引き出せます。引き出しやすくなるように応じたabbrを作りましょう。

config.fish
abbr -a --set-cursor dq dict_query '%' --output default

関数を強化する

新しい機能を追加するつもりです:

  1. インタラクティモード
  2. 調べた言葉がリストに集まる

引数とオプションがなくて関数を執行すると、インタラクティモード [14] にします。さらに関数が終了した後で調べていた言葉とその注釈をMarkdownテーブルで集めてクリップボードにコピーしてきます。

1. インタラクティモード

Ctrl-C を押してインタラクティモードを中断することができます。( SIGINT : signal interrupt

dict_query.fish
function dict_query
  set -l script_path ~/Documents/Snippets/Python/mac_dict_app.py

  if test (count $argv) -gt 0
    # ...
    # ...
  else
    # インタラクティモード
    # 出力の結果が読み方のみ
    while read -l input --prompt-str='単語またはフレーズを入力して:'
      or return 1

      set -l result (command python $script_path $input --output less)
      echo -s (set_color 00FFFF) "$result" (set_color normal)
    end
  end
end
2. 出力の結果がMarkdownテーブルに

新しい変数 $queries を加えて $input$result をそこにしばらく保存します。終了までに5回以上問い合わせがあったら printf [15]$queries からテーブルを作ってクリップボードにコピーします。

dict_query.fish
@@ -16,11 +16,29 @@ function dict_query
    else
      # インタラクティモード
      # 出力の結果が読み方のみ
+    set -l counter 0
      while read -l input --prompt-str='単語またはフレーズを入力して:'
-      or return 1
-
        set -l result (command python $script_path $input --output less)
-      echo -s (set_color 00FFFF) "$result" (set_color normal)
+
+      if test $status -eq 0
+        echo -s (set_color 00FFFF) "$result" (set_color normal)
+
+        set -f -a queries $input $result
+        set counter (math $counter + 1)
+
+        if test (math $counter % 10) -eq 0
+          echo -s (set_color 66CDAA -o) "$counter 言葉を調べていた" (set_color normal)
+        end
+      end
+    end
+
+    if test $counter -ge 5
+      begin
+        printf '| 言葉 | 注釈 |\n'
+        printf '| ---- | ---- |\n'
+        printf '| %s   | %s   |\n' $queries
+      end | fish_clipboard_copy
+      echo -s (set_color 66CDAA -o) "$counter 言葉の注釈がクリップボードにコピーされた" (set_color normal)
      end
    end
  end

その他

# 履歴をすべて削除する
history clear

# プライベートモード
fish --private

# 関数やビルトインなど無視してコマンドを強制的に執行する
command

# Shebangライン
#!/usr/bin/env fish

# viモードを入る
fish_vi_key_bindings
# デフォルトモードを帰る
fish_default_key_bindings

おわりに

最後まで読んでいただきありがとうございました。理解が及ばず、記事で取り上げるべきトピックがまだたくさん残っています。誤りや不備がありましたらご指摘ください。よろしければぜひやってみましょう。

脚注
  1. fish-shell documentation » Interactive use : Autosuggestions ↩︎

  2. Simple Python Version Management: pyenv - GitHub ↩︎

  3. fish-shell documentation » Commands » abbr ↩︎

  4. fish-shell documentation » Fish for bash users : Heredocs ↩︎

  5. fish-shell documentation » Writing your own prompt ↩︎

  6. fish-shell documentation » Commands » fish<sub>greeting</sub> ↩︎

  7. Wez's Terminal Emulator - Configuration : Changing the default program ↩︎

  8. fish-shell documentation » Introduction : Default Shell ↩︎

  9. fish-shell documentation » The fish language : Functions ↩︎

  10. fish-shell documentation » The fish language : Argument Handling ↩︎

  11. Argparse Tutorial — Python 3.14.0 documentation ↩︎

  12. fish-shell documentation » Commands » argparse ↩︎

  13. fish-shell documentation » Commands » argparse : Flag Value Validation ↩︎

  14. fish-shell documentation » The fish language : Querying for user input ↩︎

  15. fish-shell documentation » Commands » printf ↩︎

GitHubで編集を提案

Discussion