全ての記事へ戻る

記憶喪失のターミナルを天才に育てる魔法の呪文

#ubuntu#linux#shell

ターミナルは記憶喪失?

「さーて、昨日やってたあのプロジェクトの続きをやるか!」

新しいターミナルを開くと、いつも出迎えてくれるのはホームディレクトリ (~/)。 そこから cd path/to/my/super/awesome-project/src/and/so/on のような長い道のりを、毎日毎日、律儀にタイプしていませんか?

まるでターミナルが毎朝きっかり記憶喪失になっているかのようです。

「昨日どこまで進めたか、覚えていてくれたらいいのに…」

そんなあなたのための、たった数行の魔法の呪文(スクリプト)をご紹介します。

記憶力を取り戻す呪文(初級編)

まずは小手調べ。この呪文をあなたの ~/.bashrc~/.zshrc の末尾に貼り付けてみてください。ターミナルが最後にいた場所を健気に覚えてくれるようになります。

# 記憶喪失のターミナルに「最後の場所」を思い出させる呪文
# コマンド実行直前に、現在の場所をこっそりメモさせる
PROMPT_COMMAND='pwd > ~/.last_dir'

# ターミナル起動時、前回のメモを読んでその場所にワープする
if [ -f ~/.last_dir ]; then
    cd "$(cat ~/.last_dir)"
fi

天才に育てる呪文(上級編):記憶の迷宮へ、いざ出発

これを ~/.bashrc~/.zshrc に唱えれば、ターミナルを開くたびに、過去の冒険の軌跡(ディレクトリ履歴)をリストアップし、どこへでも一瞬でワープできるようになります。

さらに! 今回は特別に、複数の場所に同時にワープできる「複数選択機能」を追加しました! まるで忍者のように、複数の隠れ家を使いこなしましょう!

# 履歴ファイルの設定
DIR_HISTORY_FILE=~/.dir_history
# 記憶するディレクトリの最大数
MAX_HISTORY_SIZE=10

# コマンド実行直前に実行される関数を定義
_save_current_dir() {
    local current_dir="$(pwd)"
    local history=()
    local unique_history=""

    # 履歴ファイルを読み込み、配列に格納
    if [ -f "$DIR_HISTORY_FILE" ]; then
        IFS=$'\n' read -r -d '' -a history < "$DIR_HISTORY_FILE"
    fi

    # 配列の先頭に現在のディレクトリを追加
    history=("$current_dir" "${history[@]}")

    # 重複を除去し、最新のMAX_HISTORY_SIZE件に絞る
    # (最新のディレクトリが先頭に来るため、この順で処理)
    declare -A seen
    for dir in "${history[@]}"; do
        if [ -z "${seen[$dir]}" ]; then
            unique_history="$unique_history$dir\n"
            seen["$dir"]=1
        fi
    done

    # ファイルに書き戻す(最新MAX_HISTORY_SIZE件のみ)
    echo -e "$unique_history" | head -n $MAX_HISTORY_SIZE > "$DIR_HISTORY_FILE"
}

# PROMPT_COMMANDに記憶関数を登録
PROMPT_COMMAND='_save_current_dir'

# ターミナル起動時の処理(選択・移動)
if [ -f "$DIR_HISTORY_FILE" ]; then
    echo -e "\n**🚀 以前のディレクトリ履歴:**"

    # 履歴ファイルの内容を配列に読み込む
    # IFS=$'\n'を設定し、改行でのみ区切るようにする
    # read -d '' を使用して、ヌル文字まで(またはファイル全体)を読み込む
    IFS=$'\n' read -r -d '' -a DIR_OPTIONS < "$DIR_HISTORY_FILE"

    # 最後の選択肢(現在の場所にとどまる)を追加
    DIR_OPTIONS+=("現在の場所にとどまる")

    # selectに配列を渡す
    select dir in "${DIR_OPTIONS[@]}"; do
        if [ "$dir" == "現在の場所にとどまる" ] || [ -z "$dir" ]; then
            echo "移動しませんでした。"
            break
        elif [ -d "$dir" ]; then
            # 選択されたディレクトリへ移動
            cd "$dir"
            echo "➡️ **$dir** へ移動しました。"
            break
        else
            # このエラーメッセージは通常出なくなるはずです
            echo "無効な選択です。再度選択してください。"
        fi
    done
    echo "" # 改行を追加してプロンプトを見やすくする
fi

パフォーマンスも考慮したさらなる進化した呪文

「毎回勝手に移動されるのはちょっと...」 「履歴が増えすぎてディスクが重くなるのは嫌だ!」

そんなわがままな(もとい、こだわり派の)あなたには、こちらの上級者向け呪文を授けましょう。

この呪文は以下の点で進化しています:

  1. ディスクへの優しさ (I/O最適化): 履歴の読み書きを最小限に抑え、SSDの寿命を延ばします(たぶん)。
  2. 整理整頓 (重複排除): 同じディレクトリが履歴に何度も登場するのを防ぎます。スマートですね。
  3. 自由意志の尊重 (jコマンド): ターミナル起動時に勝手に移動しません。移動したい時は j と唱えるだけ。

Linux版

# 履歴ファイルの設定
DIR_HISTORY_FILE=~/.dir_history
MAX_HISTORY_SIZE=20


_save_current_dir_optimized() {
    local current_dir="$(pwd)"
    local history=()
    local unique_history=""
    local count=0

    # 1. 履歴ファイルを読み込み
    if [ -f "$DIR_HISTORY_FILE" ]; then
        # 配列に読み込む(改行区切り)
        IFS=$'\n' read -r -d '' -a history < "$DIR_HISTORY_FILE"
    fi

    # 2. 最新のディレクトリを先頭に追加し、重複を除去しながら配列を作成
    declare -A seen
    for dir in "$current_dir" "${history[@]}"; do
        # 最大数に達したら終了
        if [ "$count" -ge "$MAX_HISTORY_SIZE" ]; then
            break
        fi

        if [ -z "${seen[$dir]}" ]; then
            unique_history="$unique_history$dir\n"
            seen["$dir"]=1
            count=$((count + 1))
        fi
    done

    # 3. ファイルに書き戻す (1回のI/O操作に集約)
    echo -e "$unique_history" > "$DIR_HISTORY_FILE"
}

# PROMPT_COMMANDに軽量化された関数を登録
PROMPT_COMMAND='_save_current_dir_optimized'

# `j` コマンドでディレクトリ履歴を選択できるようにする関数 (上記スクリプトの後に追加)
j() {
    if [ -f "$DIR_HISTORY_FILE" ]; then
        echo -e "\n**🚀 ディレクトリ履歴:**"
        IFS=$'\n' read -r -d '' -a DIR_OPTIONS < "$DIR_HISTORY_FILE"
        DIR_OPTIONS+=("現在の場所にとどまる")

        # fzf がインストールされていれば fzf を使用
        if type fzf >/dev/null 2>&1; then
            local selected_dir
            selected_dir=$(printf '%s\n' "${DIR_OPTIONS[@]}" | fzf --prompt="Select Directory: " --height=~40% --layout=reverse --border)

            if [ -n "$selected_dir" ] && [ "$selected_dir" != "現在の場所にとどまる" ]; then
                cd "$selected_dir"
                echo "➡️ **$selected_dir** へ移動しました。"
            else
                echo "移動しませんでした。"
            fi
        else
            # fzf がなければ select を使用
            select dir in "${DIR_OPTIONS[@]}"; do
                if [ "$dir" == "現在の場所にとどまる" ] || [ -z "$dir" ]; then
                    echo "移動しませんでした。"
                    break
                elif [ -d "$dir" ]; then
                    cd "$dir"
                    echo "➡️ **$dir** へ移動しました。"
                    break
                else
                    echo "無効な選択です。再度選択してください。"
                fi
            done
        fi
        echo ""
    else
        echo "ディレクトリ履歴ファイルが見つかりません: $DIR_HISTORY_FILE"
    fi
}

Mac版

# 履歴ファイルの設定
DIR_HISTORY_FILE=~/.dir_history
MAX_HISTORY_SIZE=20

_save_current_dir_optimized() {
    # 空白を含むパスに対応するためクォートを徹底
    local current_dir="$PWD"
    local -a raw_history
    local -a unique_history
    
    # 1. 履歴の読み込み
    if [ -f "$DIR_HISTORY_FILE" ]; then
        # 改行区切りで配列に読み込む (zsh/bash両対応)
        if [ -n "$ZSH_VERSION" ]; then
            raw_history=("${(f)$(<"$DIR_HISTORY_FILE")}")
        else
            mapfile -t raw_history < "$DIR_HISTORY_FILE"
        fi
    fi

    # 2. 重複除去ロジック (常に現在のディレクトリを先頭にする)
    unique_history=("$current_dir")
    
    for dir in "${raw_history[@]}"; do
        # 空行、または現在のディレクトリと同じものはスキップ
        if [ -z "$dir" ] || [ "$dir" = "$current_dir" ]; then
            continue
        fi
        
        # 最大数に達するまで追加
        if [ ${#unique_history[@]} -lt "$MAX_HISTORY_SIZE" ]; then
            unique_history+=("$dir")
        else
            break
        fi
    done

    # 3. ファイルへの書き出し (printfで1回で実行)
    # 配列を改行で結合して出力
    if [ ${#unique_history[@]} -gt 0 ]; then
        printf "%s\n" "${unique_history[@]}" > "$DIR_HISTORY_FILE"
    fi
}

# フックの登録
if [ -n "$ZSH_VERSION" ]; then
    autoload -Uz add-zsh-hook
    add-zsh-hook precmd _save_current_dir_optimized
else
    # bash用: 重複登録を避けるため一度削除してから登録
    PROMPT_COMMAND="${PROMPT_COMMAND%; _save_current_dir_optimized}"
    PROMPT_COMMAND+="_save_current_dir_optimized"
fi

j() {
    if [ ! -f "$DIR_HISTORY_FILE" ]; then
        echo "ディレクトリ履歴が見つかりません。"
        return
    fi

    local selected_dir
    # 履歴を配列として読み込む
    local options=(${(f)"$(cat "$DIR_HISTORY_FILE")"}) 2>/dev/null || \
    IFS=$'\n' read -d '' -r -a options < "$DIR_HISTORY_FILE"

    if type fzf >/dev/null 2>&1; then
        selected_dir=$(printf '%s\n' "${options[@]}" | fzf --prompt="Go to: " --height=40% --reverse)
        if [ -n "$selected_dir" ]; then
            cd "$selected_dir" && ls
        fi
    else
        # selectを使用する場合
        PS3="移動先番号を選択 (qで終了): "
        select opt in "${options[@]}"; do
            if [ -n "$opt" ]; then
                cd "$opt" && ls
                break
            else
                break
            fi
        done
    fi
}

使い方

  1. 上記のスクリプトを .bashrc などに追記し、source ~/.bashrc で読み込みます。
  2. ターミナルで作業します(履歴が溜まります)。
  3. 移動したくなったら、j と打ってエンター!
$ j

**🚀 ディレクトリ履歴:**
1) /home/user/projects/awesome-app
2) /var/log
3) 現在の場所にとどまる
#?

数字を選べば、その場所にワープできます。

秘密のスパイス: fzf

もしあなたの環境に fzf (fuzzy finder) がインストールされていれば、この呪文は真の力を発揮します。 j コマンドを実行すると、リッチなインタラクティブ画面で履歴を検索・選択できるようになります。 これぞ、現代の魔法使いにふさわしいツールです。

ぜひ、インストールして試してみてください。世界が変わりますよ。