Works by

Ren's blog

@rennnosuke_rk 技術ブログです

`/etc/zshrc` は末尾でターミナル別拡張スクリプトを呼び出せる

/etc/zshrc を調べた時のメモ。

TL;DR

  • /etc/zshrc は末尾でterminal別拡張zshrcを呼ぶ
  • macはデフォルトで /etc/zshrc_Apple_Terminal を用意しているので、それが呼び出される

/etc/zshrc の拡張スクリプト呼び出し

/etc/zshrc は末尾でターミナル別の拡張スクリプト /etc/zshrc_$TERM_PROGRAM を呼び出すようになっており、ターミナルごとに独自の拡張をいれることが可能になっています。 環境変数 $TERM_PROGRAM にはターミナル固有の値が含まれ、例えばMacのターミナルであれば $TERM_PROGRAM の値が Apple_Terminal となっています。

$ cat zshrc | tail -n 10
[[ -n ${key[Home]} ]] && bindkey "${key[Home]}" beginning-of-line
[[ -n ${key[End]} ]] && bindkey "${key[End]}" end-of-line
[[ -n ${key[Up]} ]] && bindkey "${key[Up]}" up-line-or-search
[[ -n ${key[Down]} ]] && bindkey "${key[Down]}" down-line-or-search

# Default prompt
PS1="%n@%m %1~ %# "

# Useful support for interacting with Terminal.app or other terminal programs
[ -r "/etc/zshrc_$TERM_PROGRAM" ] && . "/etc/zshrc_$TERM_PROGRAM" # ここで拡張スクリプトが実行される

Macターミナルでの拡張スクリプト

Macターミナルでは /etc/zshrc_Apple_Terminal が用意されており、これが /etc/zshrc 末尾で呼び出されます。

# Zsh support for Terminal.


# Working Directory
#
# Tell the terminal about the current working directory at each prompt.
#
# Terminal uses this to display the directory in the window title bar
# and tab bar, and for behaviors including creating a new terminal with
# the same working directory and restoring the working directory when
# restoring a terminal for Resume. See Terminal > Preferences for
# additional information.

if [ -z "$INSIDE_EMACS" ]; then

    update_terminal_cwd() {
    # Identify the directory using a "file:" scheme URL, including
    # the host name to disambiguate local vs. remote paths.

    # Percent-encode the pathname.
    local url_path=''
    {
        # Use LC_CTYPE=C to process text byte-by-byte and
        # LC_COLLATE=C to compare byte-for-byte. Ensure that
        # LC_ALL and LANG are not set so they don't interfere.
        local i ch hexch LC_CTYPE=C LC_COLLATE=C LC_ALL= LANG=
        for ((i = 1; i <= ${#PWD}; ++i)); do
        ch="$PWD[i]"
        if [[ "$ch" =~ [/._~A-Za-z0-9-] ]]; then
            url_path+="$ch"
        else
            printf -v hexch "%02X" "'$ch"
            url_path+="%$hexch"
        fi
        done
    }

    printf '\e]7;%s\a' "file://$HOST$url_path"
    }

    # Register the function so it is called at each prompt.
    autoload -Uz add-zsh-hook
    add-zsh-hook precmd update_terminal_cwd
fi


# Resume Support: Save/Restore Shell State
#
# Terminal assigns each terminal session a unique identifier and
# communicates it via the TERM_SESSION_ID environment variable so that
# programs running in a terminal can save/restore application-specific
# state when quitting and restarting Terminal with Resume enabled.
#
# The following code defines a shell save/restore mechanism. Users can
# add custom state by defining a `shell_session_save_user_state` function
# or an array of functions `shell_session_save_user_state_functions` that
# write restoration commands to the session state file at exit. The first
# argument of the function is the pathname of the session state file in
# which to store state. e.g., to save a variable:
#
#   shell_session_save_user_state() { echo MY_VAR="'$MY_VAR'" >> "$1"; }
#
# or:
#
#   save_my_var() { echo MY_VAR="'$MY_VAR'" >> "$1"; }
#   shell_session_save_user_state_functions+=(save_my_var)
#
# During shell startup the session file is executed and then deleted.
# You may save/restore arbitrarily complex/large state by writing it
# to some other file(s) and writing command(s) to the state file that
# restore that data. You should typically use the TERM_SESSION_ID
# as part of your file or directory names.
#
# The default behavior arranges to save and restore the shell command
# history independently for each restored terminal session. It also
# merges commands into the global history for new sessions. Because of
# this it is recommended that you set HISTSIZE and SAVEHIST to larger
# values.
#
# You may disable this behavior and share a single history by setting
# SHELL_SESSION_HISTORY to 0. The shell options INC_APPEND_HISTORY,
# INC_APPEND_HISTORY_TIME and SHARE_HISTORY are used to share new
# commands among running shells; therefore, if any of these is enabled,
# per-session history is disabled by default. You may explicitly enable
# it by setting SHELL_SESSION_HISTORY to 1.
#
# Note that this uses the precmd hook to enable per-session history the
# first time for each new session; if that doesn't run, the per-session
# history won't take effect until the first restore.
#
# The save/restore mechanism as a whole can be disabled by setting an
# environment variable (typically in `${ZDOTDIR:-$HOME}/.zshenv`):
#
#   SHELL_SESSIONS_DISABLE=1

if [ ${SHELL_SESSION_DID_INIT:-0} -eq 0 ] && [ -n "$TERM_SESSION_ID" ] && [ ${SHELL_SESSIONS_DISABLE:-0} -eq 0 ]; then

    # Do not perform this setup more than once.
    SHELL_SESSION_DID_INIT=1

    # Set up the session directory/file.
    SHELL_SESSION_DIR="${ZDOTDIR:-$HOME}/.zsh_sessions"
    SHELL_SESSION_FILE="$SHELL_SESSION_DIR/$TERM_SESSION_ID.session"
    mkdir -m 700 -p "$SHELL_SESSION_DIR"

    #
    # Restore previous session state.
    #

    if [ -r "$SHELL_SESSION_FILE" ]; then
 . "$SHELL_SESSION_FILE"
    /bin/rm "$SHELL_SESSION_FILE"
    fi

    #
    # Note: Use absolute paths to invoke commands in the exit code and
    # anything else that runs after user startup files, because the
    # search path may have been modified.
    #

    #
    # Arrange for per-session shell command history.
    #

    shell_session_history_allowed() {
    # Return whether per-session history should be enabled.
    if [ -n "$HISTFILE" ]; then
        # If this defaults to off, leave it unset so that we can
        # check again later. If it defaults to on, make it stick.
        local allowed=0
        if [[ -o INC_APPEND_HISTORY ]] || [[ -o INC_APPEND_HISTORY_TIME ]] || [[ -o SHARE_HISTORY ]]; then
        allowed=${SHELL_SESSION_HISTORY:-0}
        else
        allowed=${SHELL_SESSION_HISTORY:=1}
        fi
        if [ $allowed -eq 1 ]; then
        return 0
        fi
    fi
    return 1
    }
    
    if [ ${SHELL_SESSION_HISTORY:-1} -eq 1 ]; then
    SHELL_SESSION_HISTFILE="$SHELL_SESSION_DIR/$TERM_SESSION_ID.history"
    SHELL_SESSION_HISTFILE_NEW="$SHELL_SESSION_DIR/$TERM_SESSION_ID.historynew"
    SHELL_SESSION_HISTFILE_SHARED="$HISTFILE"

    shell_session_history_enable() {
        (umask 077; /usr/bin/touch "$SHELL_SESSION_HISTFILE_NEW")
        HISTFILE="$SHELL_SESSION_HISTFILE_NEW"
        SHELL_SESSION_HISTORY=1
    }

    # If the session history already exists and isn't empty, start
    # using it now; otherwise, we'll use the shared history until
    # we've determined whether users have enabled/disabled this.
    if [ -s "$SHELL_SESSION_HISTFILE" ]; then
        fc -R "$SHELL_SESSION_HISTFILE"
        shell_session_history_enable
    else
        # At the first prompt, check whether per-session history should
        # be enabled. Delaying until after user scripts have run allows
        # users to opt in or out. If this doesn't get executed (probably
        # because a user script inadvertently removed the hook), we'll
        # check at shell exit; that works, but doesn't start the per-
        # session history until the first restore.

        shell_session_history_check() {
        if [ ${SHELL_SESSION_DID_HISTORY_CHECK:-0} -eq 0 ]; then
            SHELL_SESSION_DID_HISTORY_CHECK=1
            shell_session_history_allowed && shell_session_history_enable
            # Uninstall this check.
            autoload -Uz add-zsh-hook
            add-zsh-hook -d precmd shell_session_history_check
        fi
        }
        autoload -Uz add-zsh-hook
        add-zsh-hook precmd shell_session_history_check
    fi

    shell_session_save_history() {
        shell_session_history_enable
        
        # Save new history to an intermediate file so we can copy it.
        fc -AI
        
        # If the session history doesn't exist yet, copy the shared history.
        if [ -f "$SHELL_SESSION_HISTFILE_SHARED" ] && [ ! -s "$SHELL_SESSION_HISTFILE" ]; then
        echo -ne '\n...copying shared history...' >&2
        (umask 077; /bin/cp "$SHELL_SESSION_HISTFILE_SHARED" "$SHELL_SESSION_HISTFILE")
        fi
        
        # Save new history to the per-session and shared files.
        echo -ne '\n...saving history...' >&2
        (umask 077; /bin/cat "$SHELL_SESSION_HISTFILE_NEW" >> "$SHELL_SESSION_HISTFILE_SHARED")
        (umask 077; /bin/cat "$SHELL_SESSION_HISTFILE_NEW" >> "$SHELL_SESSION_HISTFILE")
        /bin/rm "$SHELL_SESSION_HISTFILE_NEW"
        
        # If there is a history file size limit, apply it to the files.
        if [ -n "$SAVEHIST" ]; then
        echo -n 'truncating history files...' >&2
        fc -p "$SHELL_SESSION_HISTFILE_SHARED" && fc -P
        fc -p "$SHELL_SESSION_HISTFILE" && fc -P
        fi
        echo -ne '\n...' >&2
    }
    fi

    #
    # Arrange to save session state when exiting the shell.
    #

    shell_session_save() {
    # Save the current state.
    if [ -n "$SHELL_SESSION_FILE" ]; then
        echo -ne '\nSaving session...' >&2
        (umask 077; echo 'echo Restored session: "$(/bin/date -r '$(/bin/date +%s)')"' >| "$SHELL_SESSION_FILE")
        
        # Call user-supplied hook functions to let them save state.
        whence shell_session_save_user_state >/dev/null && shell_session_save_user_state "$SHELL_SESSION_FILE"
        local f
        for f in $shell_session_save_user_state_functions; do
        $f "$SHELL_SESSION_FILE"
        done
        
        shell_session_history_allowed && shell_session_save_history
        echo 'completed.' >&2
    fi
    }

    # Delete old session files. (Not more than once a day.)
    SHELL_SESSION_TIMESTAMP_FILE="$SHELL_SESSION_DIR/_expiration_check_timestamp"
    shell_session_delete_expired() {
    if [ ! -e "$SHELL_SESSION_TIMESTAMP_FILE" ] || [ -z "$(/usr/bin/find "$SHELL_SESSION_TIMESTAMP_FILE" -mtime -1d)" ]; then
        local expiration_lock_file="$SHELL_SESSION_DIR/_expiration_lockfile"
        if /usr/bin/shlock -f "$expiration_lock_file" -p $$; then
        echo -n 'Deleting expired sessions...' >&2
        local delete_count=$(/usr/bin/find "$SHELL_SESSION_DIR" -type f -mtime +2w -print -delete | /usr/bin/wc -l)
        [ "$delete_count" -gt 0 ] && echo $delete_count' completed.' >&2 || echo 'none found.' >&2
        (umask 077; /usr/bin/touch "$SHELL_SESSION_TIMESTAMP_FILE")
        /bin/rm "$expiration_lock_file"
        fi
    fi
    }
    
    # Update saved session state when exiting.
    shell_session_update() {
    shell_session_save && shell_session_delete_expired
    }
    autoload -Uz add-zsh-hook
    add-zsh-hook zshexit shell_session_update
fi