Linuxのシェル(bash)は、実行したコマンドをすべて履歴として記録しています。この履歴を使いこなすと、「さっきの長いコマンドをもう一度」「一昨日打ったあのコマンドは何だっけ」が一瞬で解決し、作業スピードが大きく変わります。historyコマンドと、!!・!$などの履歴展開(ヒストリ展開)、そしてCtrl+Rのインクリメンタル検索が三種の神器です。
一方で、履歴展開には罠もあります。!$は「最後の引数」ではなく「最後の単語」なのでリダイレクト先まで拾うこと、そしてダブルクォートの中でも!が展開されてしまい、event not foundという謎のエラーになることです。この記事では、実機のLinux(WSLのDebian)で履歴展開を動かしながら(罠も実際に踏みながら)、historyの活用法を整理します。
historyで履歴一覧、!番号でその番号のコマンドを再実行します。!!は直前のコマンドの再実行。sudo !!が定番の使い方です。!文字列で「その文字列で始まる直近のコマンド」を再実行できます。!$は直前の「最後の単語」。リダイレクト先(/dev/nullなど)まで拾うことがあります。- ダブルクォート内でも
!は展開されます。event not foundの正体はこれです。 - 過去の検索はCtrl+R。履歴の保存先は
~/.bash_history、件数はHISTSIZEで調整します。
履歴設定を書く~/.bashrcについては環境変数とPATH、履歴の絞り込みはgrep、ディレクトリ移動の基本はcdコマンドもあわせて参考になります。
historyと!番号
historyを実行すると、番号付きでコマンド履歴が表示されます。その番号を!番号で指定すると、該当のコマンドをそのまま再実行できます。
# 履歴を一覧表示(番号付き) history # 1 ls /etc/hostname # 2 cat /tmp/hy/file.txt # 3 printf 'NUM_TEST\n' # 大量にあるときは grep で絞る history | grep ssh # 番号を指定して再実行 !2 # → cat /tmp/hy/file.txt が再実行される
実機でも、historyで番号付きの履歴が表示され、!番号でその行のコマンドが再実行されました(実行前に、展開されたコマンドが一度表示されます)。「昨日打った長いコマンド」も、history | grep キーワードで番号を見つけて!番号で呼び出せば、打ち直す必要がありません。
!!(直前の再実行)と sudo !!
!!は「直前のコマンド」に展開されます。もっとも有名な使い方が、権限エラーになったコマンドをsudo !!でやり直すパターンです。
# 直前のコマンドをもう一度実行 printf 'UNIQUE_OUTPUT\n' !! # → printf 'UNIQUE_OUTPUT\n' が再実行され、同じ出力が出る # 定番: 権限不足で失敗した直後に sudo を付けてやり直す apt install curl # 権限エラー… sudo !! # sudo apt install curl に展開される
実機でも、printf実行直後の!!が同じコマンドに展開され、同一の出力が2回得られました。展開結果は実行前に表示されるため、何が動くのかは目で確認できます。sudo !!は「sudoを付け忘れて怒られた直後」に絶大な威力を発揮する、覚えて損のないイディオムです。
!文字列(前方一致で再実行)
!文字列は、その文字列で始まる直近のコマンドを再実行します。「さっきのcatをもう一度」が!cや!catで済みます。
cat /tmp/hy/file.txt (別のコマンドをいくつか実行…) # c で始まる直近のコマンドを再実行 !c # → cat /tmp/hy/file.txt が再実行される # より特定したいときは長めに書く !cat
実機でも、!cが直近のcat /tmp/hy/file.txtに展開されて再実行されました。ただし「その文字で始まる一番新しいもの」が選ばれるため、意図しないコマンドが動く可能性もあります。rmなど危険なコマンドを!rのような短縮で呼ぶのは避け、確信が持てないときは後述のCtrl+Rで中身を見てから実行するのが安全です。
【罠】!$は「最後の引数」ではなく「最後の単語」
!$は直前コマンドの最後の単語に展開されます。「最後の引数」と説明されることが多いのですが、厳密には違います。リダイレクトの指定まで「単語」として拾われるのです。
# 正常系: 最後の引数が拾える(ファイル名の使い回しに便利) ls /etc/hostname echo !$ # → echo /etc/hostname に展開される # 罠: リダイレクト付きだと… ls /etc/hostname >/dev/null echo !$ # → echo /dev/null に展開される!(/etc/hostname ではない)
実機で確認したところ、ls /etc/hostnameの直後のecho !$は期待どおり/etc/hostnameに展開されましたが、ls /etc/hostname >/dev/nullの直後では!$が/dev/nullに展開されました。!$は「コマンドラインの最後の単語」であり、リダイレクト先もパイプ後の要素も区別しません。「さっきのファイル名を使い回すつもりが/dev/nullを操作していた」という事故につながり得るため、リダイレクトやパイプを含むコマンドの直後の!$は要注意です。確実にやるなら、展開結果を見てから実行する(!$を打った行は実行前に展開形が表示されます)か、Alt+.(直前の最終引数を挿入するreadline機能)で編集前に目視するのが安全です。
【罠】event not found の正体
echo "Hello!"のような無害に見えるコマンドで、突然bash: !: event not foundと怒られたことはないでしょうか。犯人は履歴展開です。!はダブルクォートの中でも展開されます。
# ダブルクォート内でも ! は履歴展開される echo "test !! test" # → !! が直前コマンドに展開されてしまう(内容次第で構文エラーにも) echo "Hello!abc" # bash: !abc: event not found ← !abc という履歴を探しに行った # 対策1: シングルクォートを使う(' の中では展開されない) echo 'Hello!abc' # 対策2: ! をエスケープする echo "Hello\!abc"(bashのバージョンにより挙動差あり) # もっとも確実なのはシングルクォート
この記事の検証中にも、実際に2回この罠を踏みました。ラベル表示のつもりで書いたecho "... !! ..."の!!がダブルクォート内で直前コマンドに展開され、1回目は構文エラー、2回目はevent not foundが発生しました。bashの履歴展開は、変数展開と同じくダブルクォートの中でも生きています。!を文字として使いたいとき(コミットメッセージの「Fix!」、パスワード、感嘆符入りの文字列など)は、シングルクォート'...'で囲むのが最も確実です。この振る舞い自体を止めたい場合はset +Hで履歴展開を無効化できます(スクリプト内ではもともと無効です)。
Ctrl+R(履歴のインクリメンタル検索)
履歴活用の本命がCtrl+Rです。押してから文字を入力すると、履歴をさかのぼってリアルタイムに検索してくれます。長いコマンドの再利用はこれが最速です。
# Ctrl+R を押すと表示が変わる (reverse-i-search)`': # ここで文字を打つと、履歴を新しい順に検索 (reverse-i-search)`ssh': ssh user@example.com -p 10022 # 操作: # Enter … そのまま実行 # →(または Esc) … 実行せずコマンドラインに取り出して編集 # Ctrl+R をもう一度 … さらに古い候補へ # Ctrl+G … キャンセル
Ctrl+Rはreadline(bashの行編集機能)の標準機能で、「コマンドの断片だけ覚えている」状態から一瞬で全体を呼び出せます。候補が違えばCtrl+Rを重ねて過去へさかのぼり、見つけたら→キーで取り出して編集もできます。!文字列と違って実行前に中身が見えるため、危険なコマンドの再利用でも安心です。まずはCtrl+Rを体に覚えさせるのが、履歴活用の最短ルートです。
履歴の保存先と設定(HISTSIZE・history -c)
履歴はメモリ上に保持され、シェル終了時に~/.bash_historyへ保存されます。保持件数は環境変数HISTSIZE(メモリ上)とHISTFILESIZE(ファイル)で調整でき、~/.bashrcに書いて永続化します。
# 保存先ファイル cat ~/.bash_history | tail # 保持件数を増やす(~/.bashrc に追記して永続化) export HISTSIZE=10000 export HISTFILESIZE=20000 # 現在のメモリ上の履歴を全消去 history -c # 特定の1行だけ消す(番号指定) history -d 123
実機でも、history -cを実行すると履歴が消去されました(直後のhistoryは自分自身の1行だけ)。パスワードを誤ってコマンドラインに打ってしまったときなどは、history -d 番号で該当行だけ消す、あるいはhistory -cで全消去してから~/.bash_historyも確認する、という手当てができます。日常的にはHISTSIZEを大きめにしておくと、Ctrl+R検索の資産が増えて便利です。
主な操作一覧
履歴まわりの操作をまとめます。
| 操作 | 働き |
|---|---|
history / history | grep 語 |
履歴一覧 / 絞り込み |
!! |
直前のコマンドを再実行(sudo !!) |
!番号 / !文字列 |
番号 / 前方一致で再実行 |
!$ |
直前の最後の「単語」(リダイレクト注意) |
Ctrl+R |
履歴のインクリメンタル検索(最推奨) |
history -c / -d 番号 |
履歴の全消去 / 1行削除 |
HISTSIZE / ~/.bash_history |
保持件数 / 保存先ファイル |
よくある失敗
ダブルクォート内の!でevent not found
履歴展開は"..."の中でも生きています。!を含む文字列はシングルクォートで囲みます。
!$がリダイレクト先を拾う
!$は最後の「単語」です。>/dev/null付きコマンドの直後は展開結果を確認してから実行します。
!文字列で意図しないコマンドが動く
前方一致の「直近」が選ばれます。危険なコマンドはCtrl+Rで中身を見てから実行します。
履歴に残った秘密情報を放置する
パスワード等を打ってしまったらhistory -d 番号で削除し、~/.bash_historyも確認します。
履歴件数が少なくて見つからない
HISTSIZEを増やして~/.bashrcに永続化します。
よくある質問
!!と打つと、直前のコマンドに展開されて再実行されます。実行前に展開されたコマンドが表示されるので、内容も確認できます。権限エラーの直後にsudo !!とすれば、sudo付きでやり直せる定番のイディオムです。!はダブルクォートの中でも「履歴の呼び出し」として解釈されるため、echo "Hello!abc"のような文字列で!abcという履歴を探しに行き、見つからずにevent not foundになります。!を文字として使うときはシングルクォートで囲んでください。mkdir long/pathの直後にcd !$のように、引数の使い回しに便利です。ただし「最後の単語」なので、直前のコマンドに>/dev/nullなどのリダイレクトが付いていると、実機でも確認したとおり/dev/nullのほうが拾われます。展開結果の表示を確認してから使ってください。history | grep キーワードで番号を見つけ、!番号で実行します。history -cで全消去、history -d 番号で1行だけ削除できます。履歴はシェル終了時に~/.bash_historyへ保存されるため、機密を完全に消したいときはこのファイルの中身も確認してください。保持件数はHISTSIZEで調整できます。まとめ
history+!番号、!!(sudo !!)、!文字列で履歴を再利用できます。!$は最後の「単語」。リダイレクト先まで拾う点に注意します。!はダブルクォート内でも展開されます。文字として使うならシングルクォート。- 本命はCtrl+R。実行前に中身が見える最速の履歴検索です。
- 保存先は
~/.bash_history、件数はHISTSIZE、消去はhistory -c/-d。
コマンド履歴は、使いこなすほど「打つ量」が減っていく生産性の源です。まずはCtrl+Rとsudo !!から始めて、!$の単語ルールとevent not foundの正体さえ押さえれば、履歴機能を安心してフル活用できます。
