はじめに
Bashを使っていると、過去に実行したコマンドを再利用したい場面が頻繁にあります。Ctrl+Rで履歴を検索できますが、より直感的で柔軟な履歴検索を実現するシェル関数を作成しました。
この記事では、fzf(fuzzy finder)を活用した履歴検索関数hg()の実装について、技術的な詳細を解説します。
完全なコードはGistに投稿していますので、そちらもご確認ください。
- 全履歴から選択実行するシェル関数(~/.bashrに追加する方法) · GitHub
- 全履歴から選択実行するシェル関数($HOME/.bash_functionsを読み込んで実行する方法) · GitHub
背景と課題
Bashの履歴検索の制約
コマンドライン作業で過去のコマンドを再利用する際、Bashの標準的なCtrl+R(reverse-i-search)では以下の制約があります。
- 1行ずつしか表示されず、全体を俯瞰できない
- 複数の検索ワードでの絞り込みが難しい
- 視覚的なフィードバックが限定的
fzfのkey bindings(Ctrl+R)
fzf(fuzzy finder)は、Goで実装された高速なコマンドラインフィルタリングツールです。fzfインストール時にkey bindingsを有効にするとCtrl+Rの動作がfzfを使用した履歴検索を使用できるようになります。
インストール時の出力
Downloading bin/fzf ...
- Already exists
- Checking fzf executable ... 0.66.0
Do you want to enable fuzzy auto-completion? ([y]/n) y
Do you want to enable key bindings? ([y]/n) y #key bindingsの有効化指定
Generate $HOME/.fzf.bash ... OK
Do you want to update your shell configuration files? ([y]/n) y
Update $HOME/.bashrc:
- [ -f ~/.fzf.bash ] && source ~/.fzf.bash
- Already exists:
Line 218:[ -f ~/.fzf.bash ] && source ~/.fzf.bash
~ Skipped
Finished. Restart your shell or reload config file.
source ~/.bashrc # bash
Use uninstall script to remove fzf.
比較表
| 機能 | 標準のCtrl+R | fzf版Ctrl+R |
|---|---|---|
| 表示 | 1行ずつ | 複数行を一覧 |
| 検索 | 部分一致 | fuzzy検索 |
| 操作 | Ctrl+Rで遡る | ↑↓で自由に移動 |
| 複数ワード | 不可 | 可能 |
具体例は下記の通りです。
# fzf版Ctrl+R docker compose up -d docker ps -a docker logs container_name > docker 3/150 # 全履歴から"docker"を含むものを一覧表示
hg()関数について
fzfのCtrl+Rは便利ですが、さらに以下の機能が欲しいと考えました。
- 引数で初期フィルタを指定(
hg "docker"で即座に絞り込み) - 完全一致検索("run"で検索時、"running"を除外)
- 選択後に即実行(Ctrl+Rはコマンドライン挿入のみ)
- 重複の自動削除
- カスタムカラー設定
以降は、これらを実現するhg()関数の実装について解説します。
実装の全体像
前提条件
# fzfのインストール(Ubuntu&Git) # --allオプションにより、キーbindingsと補完機能が自動的にシェル設定ファイル(`~/.bashrc`)に追加される。 git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf ~/.fzf/install --all # macOS brew install fzf
基本構造
hg() {
# 1. オプション解析(-p フラグの判定)
# 2. 履歴取得とフィルタリング
# 3. fzfによる対話的選択
# 4. 選択されたコマンドの実行
}
技術的な実装詳細
1. オプション解析の仕組み
local partial=false
local query=""
while [[ $# -gt 0 ]]; do
case $1 in
-p|--partial)
partial=true
shift
;;
*)
query="$1"
shift
;;
esac
done
技術ポイント
$#: 引数の数を取得shift: 引数リストを左にシフト(処理済み引数を削除)case構文: パターンマッチングで-pオプションを判定
2. 履歴処理のパイプライン
各コマンドの役割
history
- Bashの組み込みコマンド
~/.bash_historyと現在のセッション履歴をマージして出力- 出力形式は
行番号 コマンドの形式
GREP_COLORS='mt=01;36' grep --color=always -w "$query"
GREP_COLORS='mt=01;36'でマッチ部分を明るいシアン色(36)でハイライトmtはmatch(マッチ部分)を示す01はbold属性36はシアン色のカラーコード
--color=alwaysでパイプ経由でも色情報を保持-wで単語境界でマッチ(完全一致)- 例として"run"で検索時、"running"は除外される
tac
- テキストを行単位で逆順にする(
catの逆) - 最新のコマンドが上に来るようにする
sed 's/^[ ]*[0-9]*[ ]*//'
- 正規表現で行番号を削除する処理
^[ ]*は行頭の空白(0個以上)[0-9]*は数字(0個以上)[ ]*は数字後の空白(0個以上)
awk '!seen[$0]++'
- 重複行の削除を行う
- 動作の仕組み
- seen[$0] = 0 (初回) → !0 = true → 出力
- seen[$0]++ で seen[$0] = 1 に
- seen[$0] = 1 (2回目以降) → !1 = false → 出力しない
- 連想配列
seenで各行の出現回数を管理
fzf --height 40% --reverse --exact --ansi
--height 40%で画面の40%の高さで表示--reverseで検索バーを上部に配置(デフォルトは下部)--exactで完全一致モード(fuzzy検索を無効化)--ansiでANSIカラーコードを解釈して色付き表示
sed 's/\x1b\[[0-9;]*m//g'
3. 履歴の一覧の取得方法
完全一致モードと部分一致モードの2種類のモードで実行します。
完全一致モード(デフォルト)
hg "run" # fzf内で "test" と入力 # → "run" AND "test" を両方含む行を表示
grep -w: 単語として完全一致fzf --exact: 入力文字列が順番通りに連続して含まれる行のみ
部分一致モード(-pオプション)
hg -p "run" # fzf内で "test" と入力 # → r, u, n, t, e, s, t を順番に含む行を表示
grep(-wなし): 部分一致fzf(--exactなし): 文字が順番に含まれていればマッチ
4. カラーハイライトの実装
GREP_COLORSの設定
GREP_COLORS='mt=01;36'
フォーマットは次の通りです。
mt=属性;色コード
属性値の種類は以下の通りです。
- 00で通常
- 01でbold(明るい/太字)
- 04で下線
色コードの種類は以下の通りです。
- 30は黒
- 31は赤
- 32は緑
- 33は黄
- 34は青
- 35はマゼンタ
- 36はシアン
- 37は白
- 90-97は明るいバージョン
配置の重要性
# ❌ 効かない GREP_COLORS='mt=01;36' history | grep --color=always "run" # ✅ 正しい history | GREP_COLORS='mt=01;36' grep --color=always "run"
使用例
基本的な使い方
引数の文字列に該当する履歴を選択肢として表示する方法
完全一致モードで実行
hg "docker"
部分一致モードで起動
hg -p "docker"
全履歴の検索に対して選択肢を表示
完全一致モードで起動する
hg
部分一致モードで起動する
hg -p
実践的なユースケース
ケース1: 特定のコマンドを探す
hg "npm run" # → "npm run test", "npm run build" などが候補に # → fzf内で "test" と入力して絞り込み
ケース2: プロジェクト関連のコマンドを探す
hg -p "project" # → "cd ~/projects", "vim project.md" など # → Fuzzy検索で柔軟に絞り込み
ケース3: 長いコマンドを再利用
hg "docker compose" # → 複雑なdocker-composeコマンドを選択して再実行
ファイル管理のベストプラクティス
~/.bash_functionsへの分離
.bashrcが長くなるのを防ぐため、関数を別ファイルに分離します。
1. 関数ファイルの作成
# ~/.bash_functions を作成 vi ~/.bash_functions
hg()関数の全体をこのファイルに記述します。
2. .bashrcからの読み込み
# ~/.bashrc に追加
if [ -f ~/.bash_functions ]; then
source ~/.bash_functions
fi
3. 設定の反映
source ~/.bashrc
この方法の利点
この方法には以下の利点があります。
- 関心の分離で設定ファイルを目的別に管理できる
- 可読性が向上し
.bashrcがすっきりする - 保守性が高く関数だけを編集・無効化しやすい
- 再利用性があり別のマシンに関数ファイルだけコピー可能
業界標準の構成
多くのLinuxディストリビューション(Ubuntu等)でも採用されている方法です。
~/.bash_aliases # エイリアス用 ~/.bash_functions # 関数用 ~/.bashrc # メイン設定ファイル
パフォーマンスの考慮
大量の履歴がある場合
履歴が数万行ある場合、以下の最適化が有効です。
# 履歴サイズの制限 export HISTSIZE=10000 export HISTFILESIZE=10000 # 重複を記録しない export HISTCONTROL=ignoredups:erasedups
パイプラインの効率
各コマンドは順次実行されるため、早い段階での絞り込みが重要です。
# ✅ 効率的(早めにgrepで絞る) history | grep "docker" | tac | sed | awk | fzf # ❌ 非効率的(大量のデータをtacに渡す) history | tac | grep "docker" | sed | awk | fzf
トラブルシューティング
色が表示されない場合
原因1: GREP_COLORSの配置
# grepの直前に配置する history | GREP_COLORS='mt=01;36' grep --color=always "run"
原因2: fzfの--ansiオプション不足
# --ansiオプションを必ず含める fzf --height 40% --reverse --ansi
historyコマンドが空の場合
スクリプトファイルとして実行すると、historyコマンドは空になります。これはシェル関数としてのみ動作する制約です。
必ず.bashrcまたは~/.bash_functionsに関数として定義してください。
まとめ
このhg()関数では、以下の技術を組み合わせています。
- Bashの組み込み機能(
history,case, 条件判定) - Unix標準ツール(
grep,sed,awk,tac) - モダンなTUI(
fzfによるインタラクティブな選択) - カラーリング(GREP_COLORSによる視覚的フィードバック)
history | grepによるパイプ指定やエイリアスによる登録など効率化は可能ですが、履歴の抽出から実行までをインタラクティブ操作できるようにすることで手間も減り、効率も上がるかと思います。今後処理の追加や改善などするかもしれませんが、気になれば使用してみてください。
参考リンク
以上です。