読者です 読者をやめる 読者になる 読者になる

$ service ssh0 start

from everything import *

zsh関連の設定(2)【自分のPCで使用しているソフトウェア,設定の紹介 #6】

2016環境紹介

自分のPCで設定しているソフトウェア,設定の紹介

前回に引き続き,zshの設定ファイルについて,紹介していきたいと思います。

~/.zsh/functions以下に読み込むファイルを分けて保存しておいて,必要なタイミングで読み込まれるようにします。

~/.zsh/functions
├── alias.zsh
├── bindkey.zsh
├── colorize.zsh
├── command-not-found.zsh
├── completion.zsh
├── confirm.zsh
├── environment.zsh
├── extract.zsh
├── git.zsh
├── history.zsh
├── less.zsh
├── longrun-command-tracker.zsh
├── ls.zsh
├── man.zsh
├── peco-history.zsh
├── prompt.zsh
├── ranger.zsh
├── shtest.zsh
├── termsupport.zsh
├── tmux.zsh
├── zgen.zsh
└── zplug.zsh

github.com

environment.zsh, completion.zsh, zgen.zsh(zplug.zsh)は先に読み込まれているので,~/.zshrcの後ろの方に以下のように書いておいて,列挙したファイルを読み込みます。

# Load custom plugins
while read myfunction; do
  source "$ZSH_ROOT/functions/${myfunction}.zsh"
done < <(cat << EOF
alias
history
colorize
command-not-found
confirm
extract
git
less
ls
man
ranger
shtest
peco-history
bindkey
termsupport
prompt
EOF
)

unset -v myfunction

先に読み込まれるべきファイルと分けておいて,もっと記述を簡単にすることも出来るのだけれど,ここで書く順番に読み込まれるので,細かい調整がしやすいという利点があります。

以下ではそれぞれのファイルの内容について,ざっと見ていこうと思います。何か役にたてば幸いです。

alias

エイリアスの設定を行います。コマンドが存在するかどうかで振る舞いを変えるようにしてあり,移植性を意識しています。

  • apt
  • g for git
  • vim for nvim
  • v for vim(nvim)
  • r for ranger
  • f for thefuck
  • :q for exit
    • 癖で打ってしまうことが多いということに気づいたので...
    • かなりシームレスな感覚になれます。
  • cf-*
    • 指定した設定ファイルを編集するコマンド
      • 指定されたものがディレクトリの場合rangerで該当ディレクトリを開く
      • 設定ファイルが細分化されているものはこちらの方が便利
    • 新しくソフトウェアをインストールしたらまずここに登録する
      • どこにあるか覚える必要なし
  • rl-*
    • 設定ファイルを再読み込みするコマンドを設定
      • コマンドを覚える必要なし

_ec関数の第3引数以降には任意のコマンドを第2引数に対して実行できるようにしていますが,特に良い使い道は思い浮かびません。

# apt
if hash apt-fast 2>/dev/null; then
  alias apt-upd='apt-fast update'
  alias apt-upg='apt-fast upgrade'
  alias apt-ins='apt-fast install'
  alias apt-get='apt-fast'
else
  alias apt-upd='sudo apt update'
  alias apt-upg='sudo apt upgrade'
  alias apt-ins='sudo apt install'
fi

# git alias to "g"
alias g='git'

if hash nvim 2>/dev/null; then
  alias vim='nvim'
  alias vimdiff='nvim -d'
fi

alias v='vim'

alias r='ranger'

# I often type ":q" to exit terminal
alias :q='exit'

# s command
alias o='s'

alias info='info --vi-keys'

# thefuck (https://github.com/nvbn/thefuck)
alias f='eval "$(thefuck $(fc -ln -1 | tail -n 1)); fc -R"'

alias -g ...='../..'
alias -g ....='../../..'
alias -g .....='../../../..'
alias -g ......='../../../../..'

function _speedometer() {
  speedometer -b -rx "$1" -tx "$1"
}
alias spdmeter='_speedometer'

# enable aliases in sudo
alias sudo='sudo '

# edit configuration file by $EDITOR (vim).
function _ec() {
  local cmd alis
  if [[ ! -e "$2" ]]; then
    alias "cf-$1::NEW"="echo \"File '$2' doesn't exist. \"; \
                     confirm y \"Create new one?\" && $EDITOR '$2'"
    return -1
  fi

  alis="$1"
  if [[ -n "$3" ]]; then
    local target
    target="$2"
    cmd="$3"
    shift 3
    cmd="$cmd '$target' "$@""
  elif [[ -f "$2" ]]; then
    cmd="$EDITOR '$2'"
  elif [[ -d "$2" ]]; then
    cmd="ranger --cmd='cd '$2''"
    # cmd="builtin cd '$2'; ls"
  fi
  alias "cf-${alis}"="$cmd"
  return 0
}


_ec alias      ${ZSH_ROOT}/functions/alias.zsh
_ec completion ${ZSH_ROOT}/completions
_ec compton    ~/.config/compton/compton.conf
_ec dotlink    ~/.dotfiles/dotlink
_ec dotrc      ~/.dotfiles/dotrc
_ec env        ${ZSH_ROOT}/functions/environment.zsh
_ec functions  ${ZSH_ROOT}/functions
_ec history    ${ZSH_ROOT}/history
_ec latexmk    ~/.latexmkrc
_ec luakit     ~/.config/luakit
_ec mplayer    ~/.mplayer/config
_ec mpd        ~/.config/mpd/mpd.conf
_ec mpv        ~/.config/mpv/mpv.conf
_ec mutt       ~/.mutt/muttrc
_ec nvim       ~/.config/nvim/init.vim
_ec ncmpcpp    ~/.ncmpcpp/config
_ec plug       ~/.config/nvim/plug.vim
_ec prompt     ${ZSH_ROOT}/functions/prompt.zsh
_ec ranger     ~/.config/ranger/rc.conf
_ec ranger.d   ~/.config/ranger
_ec s          ~/bin/s_provider
_ec tig        ~/.tigrc
_ec tmux       ~/.tmux.conf
_ec turses     ~/.turses/config
_ec vim        ~/.vimrc
_ec vimcolor   ~/gitrepo/easy-reading.vim/colors/easy-reading.vim
_ec vimperator ~/.vimperatorrc
_ec w3m        ~/.w3m/config
_ec w3m-keymap ~/.w3m/keymap
_ec websearch  ~/Workspace/python/web_search/websearch/config.py
_ec xdefaults  ~/.Xdefaults
_ec xmodmap    ~/.Xmodmap
_ec xmonad     ~/.xmonad/xmonad.hs
_ec xresources ~/.Xresources
_ec zshrc      ~/.zshrc
_ec zgen       ${ZSH_ROOT}/functions/zgen.zsh

unfunction _ec

# reload configurations
rl-xdefaults() { xrdb ~/.Xdefaults ;}
rl-xmodmap() { xmodmap ~/.Xmodmap ;}
rl-xresources() { xrdb -load ~/.Xresources ;}
rl-zshrc() { exec zsh -l ;}

bindkey

キーバインドを設定します。参照される関数などが定義されている必要があるので,読み込むのは後ろのほうで。

  • ^xe: コマンドライン$EDITORで編集する。便利機能 デフォルトでは^x^eに設定してあると思うので,あまり意味はない
  • Shift + Tab: 補完でTabと逆方向に進む。割と便利
  • ^p/^n: 履歴検索時,vimっぽく動くようにする
  • ^t: fzf補完。cdするときや,ファイルがたくさんあるディレクトリでの補完など,あると便利。
  • ^r: peco-select-historyと名はついているが,実際はfzfを使った履歴サーチ。よく紹介されてる奴。
bindkey -e

# edit command line in vim:
# by pressing: ^xe or ^i
# Edit the current command line in $EDITOR
autoload -U edit-command-line
zle -N edit-command-line
bindkey '^xe' edit-command-line

# reverse-menu-complete by `Shift+Tab`
bindkey '^[[Z' reverse-menu-complete

# by C-p, C-n
bindkey "^P" history-beginning-search-backward-end
bindkey "^N" history-beginning-search-forward-end

if hash fzf 2>/dev/null; then
  bindkey '^T' fzf-completion
  bindkey '^I' $fzf_default_completion
fi

bindkey '^r' peco-select-history

colorize

色が付けられるコマンドでは色を付けるようにする。 alias.zshと統合しても良いとは思うが,目的は別。

  • mplayer
  • twitter
  • diff for colordiff
  • tree
  • grep
autoload -U colors && colors

## Alias

# mplayer alias
alias mplayer='mplayer -msgcolor'

# twitter color
alias twitter='twitter -f ansi'

# colordiff
if hash colordiff 2>/dev/null; then
  alias diff='colordiff -u'
else
  alias diff='diff -u'
fi

alias tree='tree -C'

alias grep="grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}"

command-not-found

bashを使っていると,打ったコマンドが見つからなかった時,類似する名前のパッケージをサジェストしてくれるが,その機能をzshでも使うことが出来ます(但しUbuntuでのみ)。

if [[ -x /usr/lib/command-not-found ]] ; then
  function command_not_found_handler() {
  /usr/lib/command-not-found --no-failure-msg -- "$1"
}
fi

confirm

確認メッセージを出し,(y/n)で答えさせて,その結果を利用できるようにした関数です。 他のスクリプト中で使用しています。

その他,特徴としては

  • デフォルトの答えを予め設定できる(デフォルトの方は大文字で表示される)
  • メッセージを設定できる
  • 答えが正しく入力されないときは無限ループ
  • 大文字小文字は無視
function confirm() {
  # confirm [ y | n ] [<message>]
  local yn mes __confirm ret _b _r
  _b="\e[1m"
  _r="\e[m"
  if [ "$1" = "y" ]; then
    yn="${_b}Y${_r}/${_b}n${_r}"
    mes="$2"
    ret=0
  elif [ "$1" = "n" ]; then
    yn="${_b}y${_r}/${_b}N${_r}"
    mes="$2"
    ret=1
  else
    yn="y/n"
    yn="${_b}y${_r}/${_b}n${_r}"
    mes="$1"
    ret=-1
  fi

  echo -n "$mes ($yn) "
  read __confirm
  __confirm=$(echo ${__confirm} | tr '[:upper:]' '[:lower:]')
  case ${__confirm} in
    y|yes) return 0 ;;
     n|no) return 1 ;;
       "") if [ $ret -eq -1 ]; then
             echo "Please answer with 'y' or 'n'."
             confirm "$@"
           else
             return $ret
           fi
           ;;
        *) echo "Please answer with 'y' or 'n'."
           confirm $@;;
  esac

}

extract

圧縮ファイルの展開を,拡張子によって判別して適切なコマンドで行う関数extract

対応拡張子:

tar.gz, tgz, tar.xz, zip, lzh, tar.bz2, tbz, tar.Z, gz, bz2, Z, tar, arj

実はあまり使うことのない機能だが,多分いつか役に立つときは来ると思います。

function extract() {
  case $1 in
    *.tar.gz|*.tgz) tar xzvf $1;;
    *.tar.xz) tar Jxvf $1;;
    *.zip) unzip $1;;
    *.lzh) lha e $1;;
    *.tar.bz2|*.tbz) tar xjvf $1;;
    *.tar.Z) tar zxvf $1;;
    *.gz) gzip -d $1;;
    *.bz2) bzip2 -dc $1;;
    *.Z) uncompress $1;;
    *.tar) tar xvf $1;;
    *.arj) unarj $1;;
  esac
}
alias -s {qz,tgz,zip,lzh,bz2,tbz,Z,tar,arj,xz}=extract

git

oh-my-zshのplugi以下にあるgit.zshを少し変更して使っています。長いので載せませんが,カレントディレクトリにコミットしていない変更があることを検出するなどの関数が含まれています。 プロンプトの生成時に使用しています。

history

履歴関連の設定です。

  • ファイルに保存する履歴の量を増やす
  • 重複した履歴は削除
  • 端末ごとの履歴を逐次共有
HISTFILE="$ZSH_ROOT/history"

HISTSIZE=10000
SAVEHIST=10000

# Show history
case $HIST_STAMPS in
  "mm/dd/yyyy") alias history='fc -fl 1' ;;
  "dd.mm.yyyy") alias history='fc -El 1' ;;
  "yyyy-mm-dd") alias history='fc -il 1' ;;
  *) alias history='fc -l 1' ;;
esac

setopt extended_history      # コマンドの開始時刻と経過時間を登録
setopt hist_ignore_dups      # 直線のコマンド行と同一なら登録しない
setopt hist_ignore_all_dups  # 登録済みコマンドの古い方を削除
setopt hist_ignore_space     # コマンド行の先頭が空白なら登録しない
setopt hist_reduce_blanks    # 余分な空白は詰めて登録
setopt hist_verify
setopt share_history         # ヒストリの共有

less

lessコマンドのオプションを設定

  • カラー文字を表示できるように
  • 大文字,下線文字に色を付けて表示
  • ステータスラインを冗長表示(デフォルトのファイル名表示)

あとで紹介するman.zshと組み合わせてかなり便利になりました。

# less options:
export LESS='-iMRj.5'

# colored less
export LESS_TERMCAP_mb=$(tput bold)                # begin blinking
export LESS_TERMCAP_md=$(tput bold; tput setaf 4)  # begin bold (blue)
export LESS_TERMCAP_me=$(tput sgr0)                # end mode
export LESS_TERMCAP_se=$(tput sgr0)                # end standout-mode
export LESS_TERMCAP_so=$(tput setaf 3; tput rev)   # begin standout-mode (yellow)
export LESS_TERMCAP_ue=$(tput rmul; tput sgr0)     # end underline
export LESS_TERMCAP_us=$(tput smul; tput setaf 2)  # begin underline (green)

less

lsコマンドにオプションを指定。

オプション 説明
--human-readable,-h K,M,Gなど読みやすい単位を付けて表示(with -l)
--classify,-F @(シンボリックリンク),/などを表示
--sort=version,-v 1.1.などを数字として扱ってソートする(See: info coreutils 'ls invocation' - 10.1.4 Details about version sort)
--time-style=long-iso タイムスタンプのフォーマットを"%Y-%m-%d %H:%M"に
--group-directories-first ディレクトリをはじめに表示する
--color カラー表示する
LS_OPTIONS="-hFv --time-style=long-iso --group-directories-first"

if command -p ls --color -d . &>/dev/null 2>&1; then
  LS_OPTIONS="${LS_OPTIONS} --color"
fi

alias ls="ls ${LS_OPTIONS}"

man

manコマンドをラップする関数。 引数のコマンドの種類によって,例えばビルトインコマンドであればzshbuiltinsのmanページの中でそのコマンドを検索して表示します。

ほんとちょっとしたことだけど,manコマンドをより意識して使うようになりました。

また,同名の別の種類のコマンドが存在するときには,$PERCOL(=fzf)を使って選択できるようにしています。

あと,lessコマンドに結果を渡して見ているので,lessで色付きで見れるようになっていれば,manページも色付きで表示できます。

manをもっと便利に使おう!(ビルトインコマンドに対応 + 色付きで表示) - Qiita

man zshbuiltinsを実行できるようにするためには,以下のコマンドを実行します。

$ mkdir -p ~/Downloads/zsh-doc; cd $_
$ wget http://downloads.sourceforge.net/project/zsh/zsh/5.0.2/zsh-5.0.2.tar.bz2
$ tar -xvf zsh-5.0.2.tar.bz2
$ sudo cp zsh-5.0.2/Doc/*.1 /usr/local/share/man/man1

また$MANLANGという環境変数$LANGを一時的に上書きするようにしているので,これを日本語に設定しておけば,日本語のmanページが準備されているもの(あまり多くはありませんが)は,日本語で表示させることができます。

詳しい挙動は,上にあげたQiitaの記事を見てみてください。

function man() {
  # Stock current LANG
  _LANG=${LANG}

  # set language (but if MANLANG is already set, use that)
  export LANG=${MANLANG:-${_LANG}}

  function restore_lang() {
    # restore LANG and clean up name space
    export LANG=${_LANG}
    unset _LANG
    unset -f $0
  }

  trap restore_lang 1 2 3 EXIT

  if [ ! -n "$1" ]; then
    echo "What manual page do you want?"
    return 1
  fi

  # get man type (using fzf but you can replace it with peco or percol or zaw)
  word="$(whence -wa -- $1 | uniq | fzf -1 | sed 's/: / /' | cut -d' ' -f2)"

  # if escaped, do nothing
  if [ ! -n "${word}" ]; then
    return 0
  fi

  # switch operation by word
  case ${word} in
    builtin) # built-in command
      local man_indent _space
      # TODO: get how many spaces before the commands
      man_indent=7
      _space="$(printf '%*s' "$man_indent" '')"
      /usr/bin/man --pager="less -p'^${_space}\\$1 '" zshbuiltins
      ;;
    reserved) # reserved words
      /usr/bin/man --pager="less -p'^COMPLEX COMMANDS$'" zshall
      ;;
    alias) # alias
      whence -c $1
      ;;
    function) # function
      if hash pygmentize 2>/dev/null; then
        whence -f "$1" \
          | pygmentize -l sh
      elif hash highlight 2>/dev/null; then
        whence -f "$1" \
          | highlight --out-format=ansi --src-lang=Bash
      else
        whence -f "$1" | ${MANPAGER:-${PAGER:-less}}
      fi
      ;;
    *) # try using man
      /usr/bin/man "$@"
      ;;
  esac
}

peco-select-history

よくpecoの使い方の一例として紹介されているアレです。 といっても,自分の場合$PERCOL(=fzf)を使用しているので,名前は変えるべきなんですが,そのままになっている感じです。 とっても便利で使用頻度も高いです。

function peco-select-history() {
    typeset tac
    if hash tac 2> /dev/null; then
        tac=tac
    else
        tac='tail -r'
    fi
    BUFFER=$(fc -l -n 1 | eval $tac | $PERCOL)
    CURSOR=$#BUFFER
    zle redisplay
}
zle -N peco-select-history

prompt

全部ここに貼ると長くなるので,リンクを張るだけにします。

Oh-my-zshの中に用意されていたagnoster Themeを元に,自分でいくつか変更したものとなっています。 シンプルな見た目で見やすく,また,コピペするとそのままテストになっているように工夫しました。

f:id:ssh0:20160528223354p:plain

例えば,こんな風にコマンドを実行したとして:

f:id:ssh0:20160528223325p:plain

tmux上なので,以前紹介したキーバインド^g^cで内容をコピー+vimで開くと,

f:id:ssh0:20160528223407p:plain

のように,カラーバーで見えなかった部分にコメント記号#が埋め込んでいたため,コピペで表示してもそのまま使える形式になっています。

見た目のことだけを考えるなら,いわゆるPowerlineみたいにしてもいいですし,最近まではや,▓▒░をセパレータに設定していました。

ここらへんは本当にお好みで,ってかんじですね。

僕は基本的にシンプルなのが好きってのと,下手に色があるよりは,重要な情報だけハイライトして欲しいと思うタイプなので,できるだけ色は抑えめな設定になっています。

ranger

僕が日常的によく使っているソフトウェアにrangerという端末内で動作するファイルマネージャがありますが,これをさらに便利に使うためにzshからも設定したものが以下になります。

機能としては,

  • qで抜けた時,シェルでもそのディレクトリに移動
  • サブシェルにいない時だけ新しくrangerを起動し,そうでないときはexitすることでrangerの画面に戻る
function ranger() {
  if [ -z "$RANGER_LEVEL" ]; then
    local tempfile="$(mktemp -t tmp.XXXXXXX)"
    # for manual install
    /usr/local/bin/ranger --choosedir="$tempfile" "${@:-$(pwd)}"
    # for package install
    # /usr/bin/ranger --choosedir="$tempfile" "${@:-$(pwd)}"
    test -f "$tempfile" &&
    if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then
        builtin cd -- "$(cat "$tempfile")"
    fi
    rm -f -- "$tempfile"
  else
    exit
  fi
}

termsupport

端末のウィンドウ名などを変更する機能を追加します。

これも,元々はOh-my-zshにあったものを移植してきたものです。

See: http://www.faqs.org/docs/Linux-mini/Xterm-Title.html#ss3.1

precmd_functions,preexec_functionsに設定することで,コマンドを実行するたびに,端末の名前が更新されるようになります。(実際はtmuxが間に1枚噛む)

function title {
  emulate -L zsh
  setopt prompt_subst

  [[ "$EMACS" == *term* ]] && return

  # if $2 is unset use $1 as default
  # if it is set and empty, leave it as is
  : ${2=$1}

  case "$TERM" in
    cygwin|xterm*|putty*|rxvt*|ansi)
      print -Pn "\e]2;$2:q\a" # set window name
      print -Pn "\e]1;$1:q\a" # set tab name
      ;;
    screen*)
      print -Pn "\ek$1:q\e\\" # set screen hardstatus
      ;;
    *)
      if [[ "$TERM_PROGRAM" == "iTerm.app" ]]; then
        print -Pn "\e]2;$2:q\a" # set window name
        print -Pn "\e]1;$1:q\a" # set tab name
      else
        # Try to use terminfo to set the title
        # If the feature is available set title
        if [[ -n "$terminfo[fsl]" ]] && [[ -n "$terminfo[tsl]" ]]; then
          echoti tsl
          print -Pn "$1"
          echoti fsl
        fi
      fi
      ;;
  esac
}

ZSH_THEME_TERM_TAB_TITLE_IDLE="%15<..<%~%<<" #15 char left truncated PWD
ZSH_THEME_TERM_TITLE_IDLE="%n@%m: %~"

# Avoid duplication of directory in terminals with independent dir display
if [[ "$TERM_PROGRAM" == Apple_Terminal ]]; then
  ZSH_THEME_TERM_TITLE_IDLE="%n@%m"
fi

# Runs before showing the prompt
function termsupport_precmd {
  emulate -L zsh

  if [[ "$DISABLE_AUTO_TITLE" == true ]]; then
    return
  fi

  title $ZSH_THEME_TERM_TAB_TITLE_IDLE $ZSH_THEME_TERM_TITLE_IDLE
}

# Runs before executing the command
function termsupport_preexec {
  emulate -L zsh
  setopt extended_glob

  if [[ "$DISABLE_AUTO_TITLE" == true ]]; then
    return
  fi

  # cmd name only, or if this is sudo or ssh, the next cmd
  local CMD=${1[(wr)^(*=*|sudo|ssh|mosh|rake|-*)]:gs/%/%%}
  local LINE="${2:gs/%/%%}"

  title '$CMD' '%100>...>$LINE%<<'
}

precmd_functions+=(termsupport_precmd)
preexec_functions+=(termsupport_preexec)

まとめ

以上で~/.zsh/functionsで設定している機能の紹介を終わります。

注意深く見てくださった方は気づいていただけたかもしれませんが,変数名や関数名をローカルにすることに気をつけていて,名前空間を汚すことがないようにしています。

今後も,便利そうな設定があれば随時変更していくでしょうし,今後取り組むことが変わったり,端末の使い方が変化したりした場合にはまた大きく変更があるかもしれません。

でもそれはそれでまた育てていく楽しみがあると思うので,むしろ変化があってほしいと思います。(今の環境が快適なだけに)

次回は~/bin以下に配置しているシェルスクリプトで,使用頻度の高そうなものについて紹介しようと思います。

ここまで見てくださった方,ありがとうございました。