~/.zsh_historyから削除したのにCtrl+Pで復活する理由をソースコードから追ってみた
はじめに
Zshを使っていてこんな経験ありませんか?
~/.zsh_history を編集して履歴を削除した
↓
Ctrl + P を押す
↓
普通に出てくる
「え、消したよね?」って思いますよね。
実はこれ、バグではありません。
Zshは履歴を ファイルではなくメモリ上の構造体として管理している ため、
履歴ファイルを書き換えても現在のシェルには即時反映されません。
この記事では:
- なぜ履歴が復活するのか
- Ctrl+P はどこを参照しているのか
- Zsh内部では履歴がどう管理されているのか
- 正しい履歴の削除方法
を ZshのC言語ソースコードを追いながら解説します。
現象の再現
以下の手順で簡単に確認できます。
① コマンドを実行
echo "secret_password"
② 別ターミナルで履歴を削除
vim ~/.zsh_history
該当行を削除して保存
③ 元のターミナルに戻る
Ctrl + P
結果:
secret_password
出てきます。
なぜでしょうか?
結論(先に)
理由はシンプルです:
Zshは履歴検索時に履歴ファイルではなくメモリ上の履歴構造体を参照している
つまり
履歴ファイル ≠ 検索対象
であり、履歴ファイルを元に検索しているというわけではないということです。
Zshのソースコードから確認する
Zshの履歴処理はZshの公式ソースコード(Github)の以下のファイルに主に記述されています:
Src/hist.c
Src/Zle/zle_hist.c
順番に見ていきます。
履歴はメモリ上の構造体として管理されている
履歴は単なるテキストではなく、
Zsh内部では
struct histent
という構造体として管理されています。
定義:
Src/zsh.h
つまり履歴は
履歴ファイル
↓
起動時に読み込み
↓
構造体リストとしてメモリ保持
という流れになります。
履歴ファイルを読むのは起動時など限定的
履歴ファイルを読み込む処理は以下の関数です(Src/hist.c):
readhistfile()
この関数は
- Zsh起動時
- 明示的な履歴再読み込み時
などに呼ばれます。
つまり
履歴ファイルは毎回読まれているわけではない
ということです。
ここまでで、履歴ファイルの使われ方がわかったので、実際にCtrl+Pの実装箇所を見てみましょう。
Ctrl+Pはどこを見ているのか
Ctrl+P は ZLE(Zsh Line Editor)の機能です(Src/Zle/zle_hist.c)。
関数:
uphistory()
ここでやっている処理は:
Ctrl+Pが押される
↓
履歴番号を1つ戻す
↓
メモリ上の履歴構造体から取得
だけです。
つまり、履歴ファイルは一切参照しておらず、 ファイルを書き換えても反映されないのは当然の挙動です。
Pythonで概念モデルを再現してみる
ここまでの説明は実際のコードを見ればより納得できると思います。
ただ、長いコードであるためPythonを用いてモック実装をしてみました〜。
import os
class MockZsh:
def __init__(self, history_file):
self.history_file = history_file
self.memory_history = []
self.current_index = -1
self._readhistfile()
def _readhistfile(self):
if os.path.exists(self.history_file):
with open(self.history_file, 'r') as f:
self.memory_history = [line.strip() for line in f.readlines()]
self.current_index = len(self.memory_history)
def up_history(self):
if self.current_index > 0:
self.current_index -= 1
return self.memory_history[self.current_index]
return None
# --- 実行シミュレーション ---
history_filepath = "mock_zsh_history.txt"
# 1. 事前に履歴ファイルを作成
with open(history_filepath, 'w') as f:
f.write("echo hello\n")
f.write("secret_password_command\n") # ←これを消したい
# 2. Zsh起動 (ファイルをメモリに読み込む)
zsh = MockZsh(history_filepath)
# 3. 裏でユーザーが履歴ファイルを直接編集して削除したとする
with open(history_filepath, 'w') as f:
f.write("echo hello\n") # secret_password_command を削除
# 4. しかし、現在のZshで Ctrl+P (up_history) を実行すると...
print("Ctrl+P:", zsh.up_history())
結果:
secret_password_command
出てきます。
つまり:
メモリにロード済みの履歴はファイル変更の影響を受けない
ということです。
正しい履歴の削除方法
履歴を確実に削除するには以下の手順を行います。
① 他のZshセッションを閉じる
履歴の上書きを防ぎます。
② 履歴ファイルを編集
vim ~/.zsh_history
③ メモリ履歴の保存を無効化
unset HISTFILE
④ Zshを再起動
exec zsh
これで完全に削除されます。
まとめ
- Zsh履歴はファイルではなくメモリ構造体で管理されている
- Ctrl+P は履歴ファイルを見ていない
- そのため履歴ファイルを書き換えても即時反映されない