1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

bashの複数端末で履歴共有する「おまじない」コマンドを詳しく調べてみた

Posted at

直近の実行コマンドを、別端末でもすぐに呼び出せる方法

bashで、ある端末で実行したコマンドを別の端末でも履歴からすぐに呼び出したい場合、 ~/.bashrc に次のように書くことで実現できます。

PROMPT_COMMAND="${PROMPT_COMMAND} history -a; history -c; history -r;"

※ 既に PROMPT_COMMAND に別の値が設定されている場合を想定し、その末尾に history -a; history -c; history -r; を追記しています。

この設定を各端末に source ~/.bashrc 等で反映させた後に、ある端末で例えば date を実行します。
そして、既に開いている別の端末に移動し、 Ctrl+r でリバース・インクリメンタル検索をして d と入力すると、以下のように date が最初にヒットするはずです。

(reverse-i-search)`d': date

もし date が最初にヒットしない場合は、一回 Ctrl+c でキャンセルしてもう一回 Ctrl+rd と入力すると date と出力されるはずです。

ちなみに PROMPT_COMMAND は、Bashのプロンプト (PS1) が表示される直前に毎回実行したいコマンドを指定する環境変数です。

なぜそうなるのかが気になる...

このテクニックは私がLinuxを触り始めた時に書籍か何かで知って、以後おまじないで意識せずに使っていました。ところが最近 PROMPT_COMMAND を編集した際に、 history -a; history -c; history -r; と書いた部分をコメントアウトしてしまい、直近の実行コマンドを別端末で呼び出せなくなってしまいました。

幸い設定は元に戻せましたが、これを機に history -a; history -c; history -r; が何をやってるのか? を調べてみることにしました。

要約

PROMPT_COMMANDhistory -a; history -c; history -r; を追記すると、プロンプトが表示される直前に、これらのコマンドが順番に実行される。

  1. history -a: 現在の端末で実行したコマンドが ~/.bash_history に保存される
  2. history -c: 現在の端末のメモリ上の履歴が一度クリアされる
  3. history -r: ~/.bash_history が再度読み込まれる。このファイルには、直前の history -a で保存した自端末の履歴や、他の端末から書き込まれた履歴が含まれている

この一連の処理がプロンプトごとに実行されることで、ある端末のコマンド履歴が即座に ~/.bash_history を介して他の端末にも共有される仕組みが実現される。

調査

~/.bash_historyhistory の違い

~/.bash_history は「端末で実行したコマンドの履歴を保存するファイル」です。一方、history は「コマンドの履歴を一覧表示するコマンド」です。どっちも同じ出力結果になりそうですが、必ずしもそうはなりません。

UbuntuのDockerコンテナを起動して、このことを確かめてみましょう。

docker run -dit --name ubuntu ubuntu:22.04 && \
docker exec -it ubuntu bash

以後のコマンドはコンテナ内で実行します。

まずは、適当なコマンドを実行します。

date
ls

その後に history を実行すると、これまで実行したコマンドの履歴が表示されます。

root@498d24022481:/# history
    1  date
    2  ls
    3  history

しかし ~/.bash_history はまだ存在せず、これらのコマンドは記録されていません。

root@498d24022481:/# cat ~/.bash_history
cat: /root/.bash_history: No such file or directory

Ctrl+d などでログアウトして、再びコンテナに入ります。

root@498d24022481:/# (Ctrl+d を押下)
exit

docker exec -it ubuntu bash

すると、~/.bash_history に、先ほどのシェルセッションで実行したコマンドが記録されました。

root@498d24022481:/# cat ~/.bash_history
date
ls
history
cat ~/.bash_history

再び、コンテナ内で適当なコマンドを実行します。

uname
cat /etc/os-release

この後 history を実行すると、一回目と現在のシェルセッションで実行したコマンド全てが記録されていることを確認できます。

root@498d24022481:/# history
    1  date
    2  ls
    3  history
    4  cat ~/.bash_history
    5  cat ~/.bash_history
    6  uname
    7  cat /etc/os-release
    8  history

ところが、~/.bash_history には現在のシェルセッションのコマンドは記録されません。

root@498d24022481:/# cat ~/.bash_history
date
ls
history
cat ~/.bash_history

一通り確認が終わったので、ログアウトしてコンテナを削除します。

root@498d24022481:/# (Ctrl+d を押下)
exit

docker rm ubuntu -f

このことから、~/.bash_history にはシェルセッションが終了するまでコマンドの履歴が書き込まれず、 history は現在のシェルがメモリ上に保持している履歴を表示することがわかります。このため、デフォルトでは ~/.bash_history の内容と history の出力が一致しないことがよくあります。

※ ちなみに Ctrl+r でリバース・インクリメンタル検索した際に参照する履歴は、「現在のシェルがメモリ上に持っている履歴」つまり history で出力される履歴になります。

これを踏まえて、 history -ahistory -chistory -r が何をしているのかを確かめていきます。

history -a

history --help で、-a の使い方が以下のように出力されます。

-a        append history lines from this session to the history file
          → (和訳)このセッションの履歴行を履歴ファイルに追加する

説明を見ると、あるシェルセッションで history -a を実行すると、そのセッションで実行したコマンドが ~/.bash_history に記録されそうですね。本当にそうなのか、再びUbuntuのDockerコンテナを立ち上げて確認してみましょう。

docker run -dit --name ubuntu ubuntu:22.04 && \
docker exec -it ubuntu bash

コンテナ内で適当なコマンドを実行して、最後に history -a を実行します。

ls
uname
history -a

すると、確かに ~/.bash_history に現在のシェルセッションで実行したコマンドが記録されました。

root@c92e5f3536dd:/# cat ~/.bash_history
ls
uname
history -a

history -c

以下が、helpで出力される -c の説明です。

-c        clear the history list by deleting all of the entries
          → (和訳)履歴リストをすべて削除してクリアする

「履歴リスト(history list)」は、シェルセッションのプロセスがメモリ上に保持している履歴のことを指すはずなので、これを実行すると history の履歴が全て消えそうですね。同じDockerコンテナで確かめてみましょう。

まずは、現在の history の結果を確認します。

root@c92e5f3536dd:/# history
    1  ls
    2  uname
    3  history -a
    4  cat ~/.bash_history
    5  history

現在のシェルセッションで実行したコマンドが出力されました。

続いて、history -c を実行します。

root@c92e5f3536dd:/# history -c

再び history を実行すると、確かに履歴がクリアされました。

root@c92e5f3536dd:/# history
    1  history

history -r

helpで、次のように説明されています。

-r        read the history file and append the contents to the history list
          → 履歴ファイルを読み込み、その内容を履歴リストに追加する

~/.bash_history を読んで、その内容が history に出力されるようにする」と言い換えられそうです。

まず、現在の ~/.bash_history の内容を確認します。

root@c92e5f3536dd:/# cat ~/.bash_history
ls
uname
history -a

続いて、 history -r を実行します。

root@c92e5f3536dd:/# history -r

すると、確かに3~5行目に ~/.bash_history の内容が反映されています。

root@c92e5f3536dd:/# history
    1  history
    2  history -r
    3  ls
    4  uname
    5  history -a
    6  history

一旦ログアウトして、コンテナを消します。

root@c92e5f3536dd:/# (Ctrl+d を押下)
exit

docker rm ubuntu -f

PROMPT_COMMAND に反映

これまで確認したコマンドをプロンプトで連続実行できるように、 ~/.bashrc に設定します。

三たび、UbuntuのDockerコンテナを起動します。

docker run -dit --name ubuntu ubuntu:22.04

次のコマンドを実行して、コンテナ内の~/.bashrc に反映させます。

docker exec ubuntu bash -c "echo \"PROMPT_COMMAND='history -a; history -c; history -r;'\" >> ~/.bashrc"

コンテナに入って、適当なコマンドを実行します。

docker exec -it ubuntu bash
cat /etc/os-release
date

すると、即座に ~/.bash_history に反映されるようになりました。

root@dd978f25f758:/# cat ~/.bash_history
cat /etc/os-release
date

この状態で、別の端末から同じDockerコンテナに入ります。

(端末2) docker exec -it ubuntu bash

(端末2)で、適当なコマンドを実行します。

(端末2) ls

もとの端末で一回リターンキーを押す等してプロンプトを更新後、 history を実行すると、(端末2)で実行した ls が履歴に反映されているのがわかります。

root@dd978f25f758:/# 
root@dd978f25f758:/# history
    1  cat /etc/os-release
    2  date
    3  cat ~/.bash_history
    4  ls
    5  history

おわりに

実際に手を動かしてみて、PROMPT_COMMAND='history -a; history -c; history -r;' で複数端末間でコマンド履歴を即座に共有できる仕組みを理解できました。コマンド履歴を快適かつ有効に活用するためのテクニックは他にも沢山あるので、ぜひ皆さんもご自身の環境に合った設定を探してみてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?