bashとzshの違い。bashからの乗り換えで気をつけるべき16の事柄

bashからzshに乗り換えるユーザーを対象に16の違いをまとめました。MacOSもbashからzshに変更になりましたので、zshを使い始めるにあたってのポイントを解説していきます。

目次

  1. はじめに
    1. zshとは?
  2. zshのインストール
    1. MacOS X Catalinaの場合
    2. MacOS X MojaveからCatalinaにアップデートした場合
    3. MacOS X Mojaveの場合(プリインストール)
    4. MacOS XにHomeBrewでインストールする場合
    5. Ubuntuにインストール
    6. コラム:MacOS X Catalinaでbashに戻すには
  3. bashとzshの設定ファイルの違いと引き継ぎポイント
    1. zshの起動ファイル
    2. ログインシェルx対話モードの場合
    3. 非ログインシェルx対話モードの場合
    4. 非ログインシェルx非対話モードの場合
    5. コラム:bashとzshの共存と共有設定ファイルについて
  4. bashとzshの16の違い
    1. bashとzshの制御構文系の違い
    2. bashとzshの関数定義の違い
    3. bashとzshのエイリアスの違い
    4. コラム:クォート
    5. zshのマルチリダイレクト(Multios)
    6. コマンドなしリダイレクト入力
    7. job関係
    8. 算術式と演算子
    9. 条件演算子
    10. bashとzshのプロンプトの違い
    11. bashとzshのヒストリ履歴の違い
    12. bashとzshのプロセス置換の違い
    13. bashとzshのパラメータ展開
    14. bashとzshの配列とハッシュ
    15. bashとzshのシェル変数の違い
    16. cdコマンド
    17. bashとzshのライン入力の違い
  5. zshをカスタマイズしてみよう
    1. 設定ファイルの書き方
    2. zshのエイリアス
    3. フレームワークを使ってみよう
    4. プラグインマネージャを使ってみよう
    5. まず入れておきたい3個ののプラグイン
    6. zmv(ビルトインモジュール)
  6. まとめ bashからzshへの乗り換えに向けて

はじめに

zshとは?

2019年、WWDC19の基調講演でApple社は次にリリースする「MacOS X Catalina」より標準のシェルを「zsh」に変更すると発表しました。そして現在、Macを購入したり最新のバージョンにアップデートしてターミナルを開くとbashではなくzshが起動します。

もともとMacOS Xは当初tcshであったのがv10.3 Pantherよりbashに変更された経緯があり今回それがzshにさらに変更された形になります。

こうなった経緯としてはbashのライセンスとセキュリティによる事情があります。MacOS Mojaveまでに搭載されているbashはバージョンが3で実はこれは10年以上前のもの。すでにbashのバージョンは5になっているのにここで止まっていた理由のはbash 4からGPL3が採用されたことにあります。Apple社としてはこれを受け入れられず、bashが古いままであることによるセキュリティ問題が浮上。その結果、MIT likeライセンスであるzshの採用に至ったものと思われます。

上記の件のSlashdotでの議論

zshの正式名称はZ Shell。発音はズィーシェルと言います。起源自体は古く、1990年代に、当時 プリンストン大学の学生であった Paul Falstad によって作成されました。 zshの名前は、当時プリンストン大学のティーチングアシスタントであったイェール大学教授Zhong Shaoのログイン名 ”zsh" に由来して名付けられたと言われています。

zsh.org

先発のbashから拡張された位置付けで他のシェルであるkshやtsshからも機能を取り入れたということもあり、bashでできることはzshもほぼ出来ると思って差し支えないでしょう。

ところが、ここで注意点がありまして、zshはbash全くの上位互換というわけではありません。bashのスクリプトは大抵はzshでも動きますが、一部完全に互換性がない部分もあります。

この記事では、「bashとの違い」を主軸に置いて、「bashから拡張された部分」と「bashと互換性のない部分」を焦点にzshの紹介をさせていただきます。それらを通じてzshの素晴らしさ・面白さを感じていただければ幸いです。

zshのインストール

MacOS X Catalinaの場合

すでにMacOS X Catalinaの場合はすでにzshが起動しますので特にインストールは必要ありません。bashへの戻し方は別コラムにて紹介させていただきます。

MacOS X MojaveからCatalinaにアップデートした場合

MacOS XのバージョンアップによりCatalinaにした場合は、ターミナルを開くと以下のメッセージが表示されます。

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.

デフォルトのシェルをzshにする場合は上記の通りです。

$ chsh -s /bin/zsh

と入力することでbashからzshに切り替わります。

MacOS X Mojaveの場合(プリインストール)

Mojaveの場合は標準でbashが起動しますが、じつはzshはすでにシステムに入っています。zshをシェルとして使ってみるには、ターミナルの環境設定パネルの「一般タプ」で開くシェルに「一般」タブで開くシェルに「コマンド(完全パス)」を選択し、zshのフルパス(/bin/zsh)を入力します。

MacOS XにHomeBrewでインストールする場合

以前のMacOS Xの場合はHomeBrewを使ってzshをインストールすることができます。

# インストール
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew update
$ brew install zsh

# パスを追加
$ sudo sh -c "echo '/usr/local/bin/zsh' >> /etc/shells"

# デフォルトシェルを変更
$ chsh -s '/usr/local/bin/zsh'

Ubuntuにインストール

Ubuntuへはパッケージマネージャを使ってzshをインストールします。

# インストール
$ sudo apt-get install zsh

# zshの場所を確認
$ which zsh

# デフォルトシェルを変更
$ chsh -s '/usr/local/bin/zsh'

コラム:MacOS X Catalinaでbashに戻すには

ということでライセンス・セキュリティの関係でMacOS X Catalinaよりzshになったのですが、完全に上位互換と言うわけでもなくやはりbashを使い続けたいと言う方もいらっしゃると思います。

ここではMacOS X Catalinaでbashに戻す方法を紹介します。

% chsh -s /bin/bash

上記をコマンドで入力して一旦ターミナルを閉じれば以後はbashに切り替わります。

またターミナルの設定からも行えます。こちらなら現在どちらのシェルになっているかわかりやすいかもしれません。

場所は「ターミナル」の「環境設定」の「一般」の「開くシェル」で、こちらを「/bin/bash」にすればOKです。

さらに別の方法としてシステム環境設定から行うこともできます。こちらはターミナルで設定する場合と異なり、別のターミナルをお使いの場合も有効です。

場所は「システム環境設定」の「ユーザーとグループ」を開き、ユーザーを右クリック、「ログインシェル」でこちらを/bin/bashにします。

またzshにする時は上記いづれの方法も「/bin/bash」の箇所を「/bin/zsh」にすればzshになります。

bashとzshの設定ファイルの違いと引き継ぎポイント

zshの起動ファイル

起動時に読み込まれる設定bashとzshでは全く異なります。

以下の起動要素よってそれぞれ読み込まれるファイルが変わります。

ログインシェルかどうかとは、例えばターミナルウィンドウを開いた時に自動的にzshが立ち上がった時の状態がログインシェルです。

その状態からさらにzshでスクリプトを実行された時やbash起動中などに/bin/zshと打ち込んで一時的にzshを起動した場合は非ログインシェルと呼びます。

対話モードとはターミナルで入力可能かどうかどうかです。スクリプト起動中はターミナル入力が基本的にできないので非対話モードです。

この2つの要素のそれぞれを掛け合わせた4パターンで起動時に読み込まれる設定ファイルが変化します。

(表ではzshのドット付きファイルはチルダ($HOME)と書きましたが実際は$ZDOTDIRという環境変数で変更することが出来ます)

ログインシェルx対話モードの場合

ログインシェル起動時

bash zsh
/etc/profile /etc/zshenv
~/.bash_profile ~/.zshenv
~/bash_login /etc/zprofile
~/.profile /~/.zprofile
/etc/zshrc
~/.zshrc
/etc/zlogin
~/.zlogin

bashの方はドット付きファイルに関しては一つ見つけたら以降の探索は行いません。zshは例えば.zshrcと.zlogin両方あれば両方実行します。

定番フレームワークであるOh My Zshは.zshrcを使うので、例えば自分のログイン環境だけで有効にしたい設定は.zloginに分けて書く様な使い方もできます。(ただし非ログインシェルでは実行されないので注意)

ログインシェル終了時

bash zsh
~/.bash_logout ~/.zlogout
/etc/zlogout

非ログインシェルx対話モードの場合

非ログインシェルx対話モード起動時

bash zsh
~/.bashrc /etc/zshenv
~/.zshenv
/etc/zshrc
~/.zshrc

非ログインシェルx対話モード終了時

bash zsh
なし なし

非ログインシェルx非対話モードの場合

非ログインシェルx非対話モード起動時

bash zsh
$BASH_ENV /etc/zshenv
$BASH_ENV ~/.zshenv

非ログインシェルx非対話モード終了時

bash zsh
なし なし

上記をみてわかる通り、zshの/etc/zshenvは起動時いかなる場合も読み込まれるので、なるべく記述は少なめにすることが推奨されています。

コラム:bashとzshの共存と共有設定ファイルについて

zshへ移行するにしても、しばらくはbashも使用しながら試用的に移行したい場合もあるでしょう。そんな時起動ファイルの環境変数を変更するたびにbash/zsh両方のファイルを変更するのは不効率です。

おすすめはファイルを3つに分けることです。1つはbash専用の.bash_profile、もう一つはzsh専用の.zshrc、そして両方の共通ファイルとして適当な名前でファイル、例えば.profile_commonを作ります。

そして.bash_profile、.zshrcそれぞれに以下の様に.profile_commonがあればそれをロードするコードとそれぞれのシェル固有の設定を入れておくわけです。

.bash_profileの例

#!/bin/bash

COMMON_PROFILE=$HOME/.profile_common

if [ -e $COMMON_PROFILE ]
then source $COMMON_PROFILE
fi

shopt -s extglob
export PS1="\n^[[36m(\!)[\t^H^H^H]{\$?}^[[33m\h:^[[31m\w^[[0m\n$ "

#bash固有の設定・・・

.zshrcの例

#!/bin/zsh

COMMON_PROFILE=$HOME/.profile_common

if [ -e $COMMON_PROFILE ]
then source $COMMON_PROFILE
fi

#zsh固有の設定・・・

.profile_common(bash/zsh共通設定)の例

export PATH=$HOME/.bin:$PATH
export CDPATH=$HOME:$CDPATH

export EDITOR="vim"

alias ls="ls -F"

#bash/zsh共通の設定・・・

bashとzshの16の違い

ここではbashユーザー向けにzshとの違いを16に絞って紹介させていただきます。

※本稿は主にMacOS Xユーザーを対象としていますので比較するbashはVer3系とします。

bashとzshの制御構文系の違い

if/for/while/until/case/select/(..)/{..}

これらの制御構文はbashと同じです。zshでは加えてthenやdoを省略しC言語風に記述できます。

% if [[ true ]] { echo hoge; }
hoge

% if [[ true ]] echo hoge
hoge

% for i in $(seq 0 2); { echo $i }
0
1
2

repeat

zshではrepeat制御構文が追加されています。指定した回数、続くdo・・・doneを繰り返します。bashでもforを使えば出来ましたが典型的なパターンなので簡潔に記述できます。

% repeat 3;do echo hoge;done
hoge
hoge
hoge

always(throw/catch)

zshでは他の言語の様に例外処理が実現できます。

まずは例をみてください

#!/bin/zsh

autoload -Uz catch
autoload -Uz throw

{
  echo foo
  throw 'ERROR'
  echo bar
} always {
  if catch *; then
    echo $CAUGHT
  fi
}
#結果
foo
ERROR

まずcatchとthrowは標準ではロードされていないので使うスクリプトで上記の様にautoloadを指定する必要があります。

その上で上記の様に例外ブロックを組みます。throwは例外をスルーしますのでその下のecho barは実行されません。(実際はなんらかの条件でthrowすることになります)

alwaysブロックは例外が発生した場合も必ず実行されますので、その中でcatch命令を使って例外が発生しているかどうかを判断します。*は全ての意味で$CAUGHTに、ここではERRORの文字列が入ります。catch ERRORの様にERRORの時だけ真とすることもできます。

bashとzshの関数定義の違い

一括定義

zshでは複数の名前の関数を一度に定義できます。trapの定義に有効です。

% foo bar () { echo hoge; }
% foo
hoge
% bar
hoge

無名関数

無名関数を使えます。これは非同期呼び出し用ではなくローカルスコープ形成用です。無名関数は定義時に即実行されます。

% val=hoge; function { local val=fuga; echo $val; }; echo $val;
fuga
hoge

Autoload関数

あらかじめ起動ファイルで定義しておかなくても、fpath環境変数とautoload指定により実行時にロードされる関数を作成できます。

この仕組みにより関数のモジュール化が可能となります。

fpath環境変数が示す先に関数名と同名のスクリプト(例えばhoge()の場合はhoge)を設置しておき、その中に同名の(例で言うところのhoge())を定義しておきます。

そして起動ファイルに「autoload 関数名」と記述することで関数呼び出し時に上記スクリプトが自動的に実行され定義されると言うわけです。

% cat $HOME/hoge/fuga
fuga(){echo Hello from fuga}

% fpath=($HOME/hoge $fpath)
% autoload fuga
% fuga
Hello from fuga

フック

トリガ名_functionsという配列を定義しそこに関数名を列挙することでその関数をフックできます。

フックとはその操作(トリガ)が行われた時に自動的にユーザーが関連づけた関数を呼び出す処理です。

呼び出しは元の関数が先に行われ同じ引数が渡されます。フックできるトリガは以下の通りです。

トリガ名 役割
chpwd cd時
periodic PERIODIC秒毎に定期呼び出し
precmd プロンプトが出る毎(コマンドの後)
preexec コマンド実行直前
zshaddhistory history追加時
zshexit メインシェル終了時(Subshellでない)

例えばcd時にだけディレクトリ名を表示する場合は以下の様にします。一旦関数を作って配列として代入しなければいけないことに注意しましよう。

mycd(){ echo cd to $PWD ;}
chpwd_functions=(mycd)

Trap関数

bashではtrapコマンドでフックしていましたがzshではTRAP関数を定義することでフックします。トラップの種類は上記リンク参照。

% TRAPINT() { echo hoge }
% cat  #ctrl+c
hoge

bashとzshのエイリアスの違い

基本的にbashとzshのaliasは同じですが、zshでは-gオプションをつけることでパイプやリダイレクション文字もエイリアスに含むことが出来る。

% alias -g H='|head'
% cat foo.txt H

# 上記は$ cat foo.txt |head
# になる

また-sオプションをつけることで対象の拡張子によってコマンドを起動できる(サフィックスエイリアス)

% alias txt=vim
% hoge.txt
# 上記は vim hoge.txtになる

コラム:クォート

基本的にbashと同じです。シングルクォートは変数展開しない、ダブルクォートは変数展開し、バックスラッシュでクォート出来ます。シングルクォート文字列の中にシングルクォートを入れたい場合は$'...'としてバックスラッシュを使います。

zshのマルチリダイレクト(Multios)

マルチリダイレクト出力

zshでは以下の書き方をすることで同時に複数のファイルにリダイレクトで書き出すことが出来ます。

% cat foo.txt >hoge.txt >fuga.txt

上記の例ではhoge.txtとfuga.txtは同じ内容になります。

bashでもteeコマンドを使えば同等のことが出来ますが、zshの書き方はより自然ですね。追記も同様に行えます。

% cat foo.txt >>hoge.txt >>fuga.txt

teeコマンドの様に標準出力に出しつつファイルに保存したい場合は以下の様に書きます。

% cat foo.txt >hoge.txt | cat

注意点としてはMultiosを使った場合はサブプロセスとして実行され即時コマンドが終了することです。次のコマンドで出力されたファイルを受け取るためサブプロセスではなくカレントプロセスで実行したい場合は以下の様にする必要があります。

% {cat foo.txt } >hoge.txt >fuga.txt

マルチリダイレクト入力

同じくzshでは入力リダイレクトを複数使えます。これは単にcatでつなげてパイプで入力するのと同等です。

% sort <hoge.txt <fuga.txt
# 以下と同じ
% cat hoge.txt fuga.txt | soft

コマンドなしリダイレクト入力

% <hoge.txt

上記の様にコマンドなしの場合は

% cat hoge.txt | more

として扱われます。これはNULLCMD(デフォルト:cat)とREADNULLCMD(デフォルト:more)で設定出来ます。

job関係

jobとはコマンドの終了を待たずにバックグラウンドで動作させる機能です。

例えば以下では何もせずに100秒待つプロセス(job)と50秒待つプロセス(job)を起動しjobsコマンドで一覧、2番目のjobを途中停止しています。

% sleep 100 &
% sleep 50 &
% jobs
[1]  - running    sleep 100
[2]  + running    sleep 50

% kill %2
% jobs
[1]  + running    sleep 100

上記job操作はbashでも同じですが、zshの場合は以下の様に?をつけることで先頭だけでなく部分一致でjobを指定できます。

 $ sleep 100 & 
 $ %?lee

またzshはコマンド実行時に&!とエクスクラメーションをつけることで、jobsに出ない様にバックグラウンドプロセスを起動できます。

算術式と演算子

基数

zshでは8進数や16進数を扱えます。

% echo $(([#16] 10 ))
16#A

16は基数で16進数表記しろという指定です。setopt cbasesしておくことでC言語風の出力もできます。

% setopt cbases
% echo $(([#16] 10 ))
0xA

プレフィックスを消すこともできます。

% echo $(([#16] 10 ))
A

演算子と小数点

zshではbashの演算子に加えてexponentや比較演算子’が加えられています。また小数点演算も可能です。

% echo $((2.0 + 2.1))
% echo $((2 ** -1))  #-> 0.5
% echo $((2 == 2))  #-> 1

変数は最初の代入で整数かfloatかが決定されるので注意してください。

条件演算子

条件演算子はbashとzshはぼぼ同じですが、zsh文字列比較は完全一致ではなくパターン一致となります。また==や=の代わりに=~を使うことで正規表現も使用可能です。

% [[ HOGE = HO* ]];echo $?
0

% [[ HOGE =~ HO.. ]];echo $?
0

bashとzshのプロンプトの違い

プロンプトの書き方はbashとzshで全く異なります。

どちらもPS1、PS2・・で変更する点は同じですが置換文字が異なりzshではさらに複雑なことが出来ます。zshのプロンプト用の置換はprintビルトイン関数でも利用可能です。

こちらは後ほど詳しくご説明します。また内容を知らなくてもフレームワークやプラグインマネージャを使って他のユーザーが作ったプロンプトを簡単に利用することができます。

bashとzshのヒストリ履歴の違い

ヒストリ履歴とはhistoryコマンドで表示される今まで入力したコマンドのリストです。

例えば前に打ち込んだコマンドの引数だけを再利用したい場合に便利です。

% history
 3986  ls -l
 3987  ls *.png

% rm !3987*
# →rm *.png と同じ(3987番から引数のみを再利用の意味)
% rm !*
# 直前のコマンドの場合は番号は省略可能

ヒストリ履歴はほぼbashとzshで同じですが、さらにmodifiers(修飾子)が拡張されています。

修飾子 機能
a ファイル名を絶対パスに
A aと同じだがシンボリックリンクを解決
c コマンド名を絶対パスに
l 小文字に変換
p aと同じだが.や..は残す
Q クォートを1段外す
u 大文字に変換

modifiersは以下の様に使います。

% history
 3992  echo a.jpg
 3993  echo b.txt

% ls -l !3992*:a
/Users/user/Desktop/sd/a.jpg

#:aのmodifiersで絶対パスに

bashとzshのプロセス置換の違い

プロセス置換とはファイルの代わりにコマンドの出力結果をまるでファイルの様に別のコマンドに渡せる便利な機能です。

以下の様なプロセス置換はbashとzshで共通です。

% cat <(cat hoge.txt) |tee >(sort)

上記はFIFOを使って行われますが、zshでは以下の様に書くことでテンポラリファイルを介すことが出来ます。これはFIFOではなくファイルでシークしたい場合に利用されます。

% cat =(cat hoge.txt) | tee >(sort)

bashとzshのパラメータ展開

パラメータ展開とは変数を使用する時に加工して展開する機能でbashからありました。

例えば以下の様な記述がパラメータ展開です。

% a=foo.jpg
% echo ${a%jpg}.png  #後方最短一致でjpgまでを後ろから取り除く
foo.png

% echo bar${a#foo}  #前方最短一致でjpgまでを後ろから取り除く
bar.jpg

ちなみに上記は最短一致ですが%や#を2つ並べる事で最長一致にしたり文字列を検索して置換したりできます。(詳しくはbashの参考書などを参照)

パラメータ置換に関してはぼぼbashとzshで同じです。zshには以下の拡張がされています。

% echo ${+a}
#aがセットされていたら1、そうでなければ0

% a=(fuga hoge fuga)
% echo ${a:#fuga}
hoge
#配列から指定した文字列に一致した要素を消す

% a=(hoge fuga moga)
% b=(hoge fuga)
% echo ${b:|a}
moga
#配列bから配列aの要素を消す

% echo ${b:*a}
hoge fuga
#配列bと配列aの共通要素を表示

% a=(1 2 3)
% b=(4 5 6)
% echo ${a:^b}
1 4 2 5 3 6
#配列aとbの要素を先頭から交互に表示(zip処理)

% a=hogehogehoge
% echo ${a/hoge/FUGA}
FUGAhogehoge
#↑bash共通のの文字列一致置換

% echo ${a//hoge/FUGA}
FUGAFUGAFUGA
#一致した全て文字列を全て置換

% echo ${a:/hoge/FUGA}
hogehogehoge
#完全一致でないと置換しない

% a=(1 2 3)
% echo hoge${^a}.png
hoge1.png hoge2.png hoge3.png
#配列それぞれの要素を前後文字列と接続し表示

% IFS=','
% a="1,2,3"
% echo ${=a}
1 2 3
#IFSで分割

% a="*.txt"
% echo ${~a}
hoge.txt fuga.txt moga.txt
#ワイルドカードに一致するファイルを展開させる

bashとzshの配列とハッシュ

bashとzshの配列の添字の違い

bashの配列は0から、zshはなぜか1から始まります。

% a=(A B C)
% echo $a[1]
A #-> zshの場合
B #-> bashの場合

zshのハッシュ配列の使い方

zshではハッシュ構造が使えます。typesetで宣言が必要です。

% typeset -A a
% a[hoge]=1
% a[fuga]=2
% echo $a[hoge]
1

% echo $a[*]
hoge fuga    #キーを列挙

% echo $a[@]
1 2         #値を列挙

bashとzshのシェル変数の違い

シェルがセットする変数(引数やステータス系)

以下はbash/zshで共通です。

シェル変数 内容
$0 スクリプト名
$1~$9 引数。例えばmyscript foo barの時は$1=foo;$2=barとなる
$# 引数の数
$* すべての引数(1つの文字列として)
$@ すべての引数(分割/forループなどで分割処理される)
$? コマンド終了値(0で成功)

$$スクリプトのプロセスID $!|最後に実行したコマンドのプロセスID

加えて以下がzsh独自のシェル変数です。

変数 機能
argv $*と同じだがグローバル変数
status $?と同じ
ARGC $#と同じ
pipestatus 最後のパイプラインの全コマンドのステータス

その他シェル変数

PWDやIFSなど大部分は共通ですが、bashとzshで異なる部分も多くあります。

cdコマンド

cd -で前のディレクトリに戻る点、cdした時に見つからなかったらcdpath(zshは小文字)を検索するところまではbashとzshは同じです。

ディレクトリ名だけでcd

zshではオプションで様々なcdについての設定が出来ます。

% setopt AUTO_CD
% some_directory
~/some_directory

setopt AUTO_CDでcdコマンドなしでディレクトリ名だけでcdできる様になります。

自動的にpushd

% setopt AUTO_PUSHD
% cd a
% cd b
% cd c
% dirs
~/c ~/b ~/a

% cd +2
~/a

setopt AUTO_PUSHDで自動的にpushdする様になります。cdに+と数値をつけることでdirsの左から好きな番号のディレクトリにcdすることが出来ます。setopt PUSHD_SILENT しておけば、移動の度にディレクトリスタックを表示するのを止めることが出来ます。

似た名前のディレクトリの水平移動

% mkdir testA testB
% cd testA
% cd A B     #-> 現在のディレクトリ名のAをBに置換して移動、つまりtestBにcd

cdに2つ引数を並べることで、現在のディレクトリの左の文字列を右の文字列に置換してcdしてます。例えばhoge1,hoge2,hoge3...の様に水平に連番に並んでいるディレクトリ間を移動する時便利です。

深いディレクトリへの移動

% mkdir -p foo/bar/baz
% cd **/baz

これは次のファイル名補完の機能ですが、アスタリスク2つ並べることで深いディレクトリも検索して一気にcdできます

bashとzshのファイル名補完の違い

特殊文字

zshはbashの* / ? / [...] と[:alpha:]などの文字クラスに加えて以下のファイル名補完用の文字が用意されています。


% touch hogeA hogeB
% echo hoge[^B]
hogeA
# [!...]
# [^...]
#...を含まない文字にマッチ

% touch hoge1 hoge2 hoge3
% echo hoge<1-2>
hoge1 hoge2
#数値範囲にマッチ

% touch hogeA fugaA
% echo (hoge|fuga)A
hogeA fugaA
#グルーピング

% echo *(/)   #ディレクトリのみにマッチ
% echo *(.)   #ファイルのみにマッチ
% echo *(@)   #シンボリックリンクのみにマッチ
% echo *(m+30)   #30日前より過去に変更されたファイルにマッチ(m=変更/a=アクセス/c=作成)
% echo *(m-30)   #30日前より未来に変更されたファイルにマッチ
% echo *(cm-30) #上記の単位指定。30分前より後に作成されたファイルにマッチ(h=時/m=分/s=秒/w=週/M=月)
% echo *(L+1000)   #1000バイトを超えるファイルにマッチ
% echo *(LM+10)   #10Mバイトを超えるファイルにマッチ(M=メガ/K/Gも使用可能)

その他setopt EXTENDED_GROBでさらに使える特殊文字やフラグが増えます。

深いディレクトリ

cdの時と同じく深いディレクトリを探索させる出来ます。

% vim **/*.c

さらにsetopt EXTENDED_GROBを有効にすることでfindコマンドの様にディレクトリだけを選んだりファイルだけを選んだりすることができる様になります。

bashとzshのライン入力の違い

bashではemacs風とvi風が選べましたがzshでも同様にそれぞれを選べますのでそれほど違和感はありません。

% bindkey -e 
#emacs風のキーバインディングにする

zshをカスタマイズしてみよう

設定ファイルの書き方

結局zshではどこに設定を書けばいいのか

「bashとzshの設定ファイルの違いと引き継ぎポイント」の項でも紹介しましたがzshは様々な設定ファイル名が探索されます。

自分で使いやすい様にカスタマイズするという観点では「ログインシェル」かつ「対話的モード」でのみ読み込まれる設定ファイル、そしてログインユーザー固有の設定である「.zprofile」、「.zshrc」、「.zlogin」(いずれもホームディレクトリ)を使用することになります。(実行もこの順番になります)

「ログインシェル」ではなく「対話的モード」の場合とは例えばbash上からzshと打ち込んで一時的にzshを利用する場合になります。こちらは上記のうち「.zshrc」のみが読み込まれます。

つまり、.zshrcに記述しておけばターミナルを開いた時もコマンドでzshを起動した時も読み込まれるということになりますので特にコマンドで開いた時に実行したくないのでなければ.zshrcに書いておけばよいということになります。

zshの設定ファイルの内容

設定ファイルでよくある構成は以下の通りです。

下記に典型的なサンプルを掲載します。

~
~
#!/bin/zsh


#
# プラグインマネージャによる指定と読み込み
#

source ~/.zplug/init.zsh

zplug "modules/history", from:prezto # https://github.com/sorin-ionescu/prezto/blob/master/modules/history/init.zsh
zplug "modules/directory", from:prezto # https://github.com/sorin-ionescu/prezto/blob/master/modules/directory/init.zsh

if ! zplug check --verbose; then zplug install;fi
zplug load


#
# ビルトインのモジュールの指定
#

autoload -Uz zmv

#
# オプション指定やキーバインディング指定
#


setopt clobber # >による上書きを許可
bindkey -e  #emacsバインディング

#
# 環境変数指定(PATHは実行ファイル、CDPATHはcdでカレントディレクトリになかった場合の探索パス)
#

export PATH=$HOME/.bin:$PATH 
export CDPATH=$HOME:$CDPATH

#
# エイリアスやファンクションの指定
#

alias g="grep"
alias m="make"
alias zmv='noglob zmv -W'
alias -g F='$(fzf)'

mcd(){
    mkdir $1
    cd $1
}

#
# プロンプトの設定
#

export PS1="%(?,,^[[31m:(^[[0m)
^[[36m(%!)[%T]{%j}^[[33m%m:^[[31m%~^[[0m

zshのプロンプト

bashでもプロンプト(コマンド入力時の待機表示)にはカレントディレクトリや時刻など情報を含められました。zshでももちろんそれらの情報は表示できますがbashとの互換性はなく独自の記述方法をすることになります。

プロンプトを設計する時便利なのがprintコマンドです。

print -P "%M(%h) >"

上記の様に二重引用符の中に試しにプロンプトを入れてみて表示を試すことができます。上の例ではマシン名とヒストリ番号を表示します。

プロンプト設計が終わったら設定ファイルにその内容を追加すればOKです。

export PS1="%M(%h) >"

以下に代表的な置換記号を紹介します。

他にも条件判定など複雑なことができます。

詳しくはPrompt Expansionを参照ください。

なお、これら内容を知らなくても後ほど紹介するフレームワークやプラグインマネージャを使って他のユーザーが作ったプロンプトを簡単に利用することができます。

zshのエイリアス

よく使うコマンドは短くタイプできる様にしておくと便利です。たとえば「m」と打つだけで「make」コマンドを起動しなど。

alias m="make"

zshのエイリアスの構文はbashと互換性がありbashの設定をそのまま使えます。

さらに面白いのはコマンドの先頭だけではなく、コマンドの途中にエイリアスを入れることができるのです。

例を挙げます。

alias -g F='$(fzf)'

vim F

上記の例ではvim FのFは$(fzf)に展開されます。つまりvim $(fzf)となります。fzfは現在のディレクトリのファイルをカーソルで選んで選んだ結果を表示するツールです(homebrewなどでインストール可能)。つまりvim Fと入力するだけでファイル選択画面が起動、例えばhello.htmlを選んだらvim hello.htmlが実行されると言うわけです。

この様に引数としてコマンドの結果を渡したい時に便利な機能です。

フレームワークを使ってみよう

「フレームワーク」はざっくり言うと「出来合いの設定ファイル」です。よく使うオプションが有効になったり便利な関数や補完が含まれており、プラグインシステムにより取捨選択できます。

またフレームワークのプラグインはフレームワーク自体を入れなくてもプラグインマネージャから個別に利用可能です。

この記事としてはフレームワークをいきなり入れて一発でオススメ設定にするのではなく、zshを自分で学習しながらプラグインシステムを使って少しずつフレームワークの中のプラグインだけをを利用してくことをおすすめしております。

なのでここではフレームワークについては概要だけを説明させていただきます。

zshのフレームワークは有名なもので2種類あります。

Oh My Zsh

古くからあるzsh用のフレームワークです。インストールにより.zshrcが専用のものに書き換わり、そこでプラグインの読み込みなどの設定を行います。

上の例で使っていた.zshrcは.zshrcがOh My Zshのフレームワークで上書きされることに注意してください。zshの場合は複数起動ファイルがあっても読み込まれますので.zloginに退避させるのも良いでしょう(その場合はログインシェルでないインタラクティブモードでは読み込まれないので注意)。

Oh My Zsh一つのgitリポジトリで複数の作者がプラグインをメンテしているのが特徴です。

Prezto

Oh My Zshの後継のフレームワークです。Oh My Zshの欠点として起動が遅いこと、プラグインが全て一つのリポジトリで管理されている事が挙げられます。

そこでPreztoはプラグインをgitのサブモジュールとして分けて管理し、起動についても必要最低限のみ読み込む事で改善を図っています。

Preztoを使う場合は全ての起動ファイルが使われてしまうため、上記の様にbashと共通設定を持ちたい場合は専用のカスタムファイルである.zpreztorcと言う専用ファイルから読み込む様にします。

またプロンプトを書き換えるテーマについても独自仕様であったOh My Zshに比べ、zsh本来のpromptコマンドで切り替えられる仕様なのでより自然に変更可能で、プレビューもできます。

プラグインマネージャを使ってみよう

上記どちらにせよフレームワークを使う場合は起動ファイルは書き換えられてしまいます。それが気持ち悪い場合はプラグインマネージャを使います。プラグインマネージャのインストールは.zshrcにsourceでロードする事で行いますので起動ファイル全体を書き換えられることはありません。

また上記の2つのフレームワークに対応しており、どちらのプラグインも利用可能です。一発でオススメ設定にするのではなく、zshを自分で学習しながら少しずつプラグインを利用したい向きには最初からフレームワークではなくプラグインマネージャ経由でOh My ZshやPreztoのプラグインを使うことをおすすめします。

Antigen

zshで最も有名なプラグインマネージャです。インストールは.zshrc全体を書き換えるのではく、1行だけ加えて、続けて使いたいOh My ZshやPreztoのプラグインを書き最後にapplyするだけです。フレームワークは自動的に読み込まれるのでインストールを意識する必要はありません。また独立したgitのリポジトリをプラグインとして指定して利用することも出来ます。

後発のプラグインマネージャほど柔軟性や高速性はありませんが使い方がシンプルなので学習コストがかからずOh My ZshやPreztoプラグインを簡潔に使いたいだけの場合はAntigenが未だおすすめです。

zplug

並列処理による高速性と柔軟性を兼ね揃えた和製プラグインマネージャで、世界中で使われています。antigenよりはやや学習コストがややかかりますがバージョンロックやbash用のプラグインもロード出来たりと柔軟性が非常に高いです。

zplugin

後発のプラグインマネージャです。zplugよりさらに高速と言われています。プラグインの修飾が1行で書けないのが難点ですが非常に多機能で細かい設定が可能です。学習コストは一番かかると思います。

その他のプラグインマネージャ

その他、高速化されたantigenなど多数。

結局zshにはどのプラグインマネージャやフレームワークがいいのか

個人的な意見としては設定ファイルを一気に書き換えてしまうフレームワーク(Oh My ZshやPrezto)をいきなりそのまま使うのはおすすめしません。それはzsh自体の理解の妨げになるからです。

まずは自分で素の状態でzshの設定ファイルを触ってみて、上記フレームワークのプラグインのソース(非常に簡単な構造です)を参考にしながら出来ることを理解するべきです。特にoh-my-zsh/lib辺りが見本として最適です。Prezto/moduleにもhistoryやdirectoryなど設定関係のセットがあり参考になります。

そしてフレームワークではなくプラグインマネージャを利用して「あくまで自分のやりたい事を簡潔に」するのが、正しい流れです。

プラグインを設定ファイルに書くときのアドバイスとしてはソースのURLをコメントで入れておくと中で何をしているか後から確認できるのでオススメです。

ではプラグインマネージャはどれがいいのか?と言う話になってきますが、最初は学習コストのかからないantigenで簡単にOh My ZshやPreztoのプラグインを触ってみるのが良いかと思います。2つのフレームワークのプラグインだけを使う範囲では一番簡潔に書けます。

その上でスピードや柔軟性、保守性が欲しくなった場合はzplugに移行するのが遠回りですが着実な手順でzplugのありがたみも理解できるという訳です。

MacOSで使う場合はは最初からzplugを使ったほうがいい

上ではまずはantigenをと書きましたが、MacOSの場合は不安定な箇所がありましたのでMacの場合はzplugから使い始めることをオススメします。

homebrewのインストールも対応していますが、インストールフォルダが標準的な$HOME/.zplugにならないので以下のインストールスクリプトを使ったほうがいいでしょう。

$ curl -sL --proto-redir -all,https https://raw.githubusercontent.com/zplug/installer/master/installer.zsh | zsh

preztoのプラグインだけを読み込む最も簡単な.zshrcのサンプルも掲載します。

source ~/.zplug/init.zsh

zplug "modules/history", from:prezto # https://github.com/sorin-ionescu/prezto/blob/master/modules/history/init.zsh
zplug "modules/directory", from:prezto # https://github.com/sorin-ionescu/prezto/blob/master/modules/directory/init.zsh
zplug "modules/osx", from:prezto, if:"[[ $OSTYPE == *darwin* ]]"

if ! zplug check --verbose; then zplug install;fi
zplug load #--verbose

historyとdirectoryはzshの基本的な履歴機能と自動pushd機能を有効にしてdでディレクトリスタック表示、数値だけでティレクトリスタックにジャンプ出来るようにするシンプルなプラグインです。非常に簡単なソースなので上のリンクから確認してみてください。

osxはMacOSのファインダーとの連携で、ファインダーで表示しているディレクトリやファイルをpdf/pds/cdsコマンドで取り込むことができます。同じく上のPreztoのリンクからドキュメントを参照してください。

まず入れておきたい3個ののプラグイン

ここでは非常によく使うスタンダードなプラグインを3個に厳選してご紹介します。

これらは前述の「フレームワーク」にはすでに入っていますが「プラグインマネージャ」を使って自分で選択する場合は、ぜひ入れておきたいプラグインです。

(「フレームワーク」を使っている場合はすでに同等のものが入っています。)

プラグインマネージャはzplugを使用します。他のプラグインマネージャをお使いのマネージャに合わせて使う場合は適宜変更してください。

zplugの場合はzplugコマンドでプラグインを指定した後に以下の様に忘れずにロードする様に設定ファイルに記述してください。

zplug <モジュール名>,<ソース名>
zplug <モジュール名>,<ソース名>
.
.

# プラグインをロード
if ! zplug check --verbose; then zplug install;fi
zplug load #--verbose

modules/history

まずはpreztoフレームワークよりhistoryプラグインを紹介します。

zplug "modules/history", from:prezto

zshには様々なコマンドヒストリに関する機能があります。

例えばターミナルを複数開いて並行してコマンド実行してもそれぞれのヒストリを共有したりすることが出来ます。つまりターミナルAでlsと打ち込んで実行したらターミナルBのヒストリにlsが現れる様にする(SHARE_HISTORY)ことが出来ます。

また重なったコマンドは省略して1行のヒストリにするトリム機能、たとえばlsを実行してからlsと実行してもヒストリは1行lsが追加されるのみになる機能(HIST_IGNORE_DUPS)、

さらにはヒストリを!コマンドで呼び出してもbashの様にいきなり実行されずに一度編集可能状態で表示し、間違ったヒストリの呼び出しを防いだり小変更できる機能(HIST_VERIFY)

などなど。

しかしながらこれらはデフォルトでは有効にはなっておらず、本来は設定ファイルにsetopt HIST_VERIFY;のように一つ一つ記述しなければいけないのです。

またヒストリの個数もデフォルトではやや少なめなのでHISTSIZE=10000としてヒストリのサイズを大きくすると便利です。

これらのいわば「定石」とも言える設定をひとまとめにしたのがこのhistoryプラグインです。

またhistory-statというコマンド(エイリアス)も追加されますので、これを実行することでよく使うコマンドの統計が見れるのも面白いですね。

以下リンクに具体的にどのような設定がされるのかが記述されていますので、興味があればチェックしてみてください。

https://github.com/sorin-ionescu/prezto/blob/master/modules/history/init.zsh

modules/directory

こちらもpreztoフレームワークよりdirectoryモジュール。

zplug "modules/directory", from:prezto

ヒストリと同じくzshにはディレクトリ移動であるcd、pushd、popdに関する様々な機能があります。

例えばfooというディレクトリがあるとしてcd fooではなくfooとだけ打ち込むだけでディレクトリ移動ができるAUTO_CD、

cdするだけで自動的にpushdのスタックにディレクトリが格納されるAUTO_PUSHD機能、またそれらの「重なり」を無視してくれるPUSHD_IGNORE_DUPS機能などです。

これらもやはり設定ファイルにてsetoptを並べる必要があるのですが、やはり「定石」的な設定というものがありそれらをまとめたのがこのmodule/directoryプラグインとなります。

またdコマンド(エイリアス)も追加されます。これは直近のヒストリを一文字のdだけで表示するコマンドです。

$ d
0    ~/Desktop/blog/raku
1    ~/Desktop/blog
2    ~/Desktop

そして便利なのが、例えば上の1番がついているディレクトリ、~/Desktop/blogに移動したい場合はその番号、「1」だけを入力してリターンを打つだけでそのディレクトリに移動できるのです。

これもdirectoryプラグインの機能です。

以下リンクに具体的にどのような設定がされるのかが記述されていますので、興味があればチェックしてみてください。

https://github.com/sorin-ionescu/prezto/blob/master/modules/directory/init.zsh

modules/osx

MacOS X限定ですがこちらも面白いモジュールです。

zplug "modules/osx", from:prezto

osxモジュールはターミナルの外側のFinderと連携を可能にします。

例えば、ターミナルを開いた状態でFinderでデスクトップのblogフォルダが開かれているとしましょう。そこで

$ cdf

と入力するとターミナルの方が先ほどFinderで開いたblogディレクトリの方に移動します。Finderで一覧しながらフォルダを移動し、目的のフォルダのファイルをコマンドラインから一括処理したい場合などにとても便利です。

似た感じでpfsコマンドがあります。こちらはFinderで「選択されたファイル」を表示します。表示するだけなので何に使うの?と思いますよね。これは以下の様に使います。

$ mv $(pfs) here/

上記はFinderで選択されたファイルをターミナルの現在のディレクトリにあるhere/ディレクトリに移動します。もちろん複数ファイルでもOKです。(同様にFinderの現在のディレクトリを表示するpfdコマンドもあります)

qlコマンドも便利です。こちらはターミナルで指定したファイルをQuickLookで開きます。例えば

$ ql document.pdf

はカレントディレクトリにあるdocument.pdfの中身を一時的なウィンドウで確認することが出来ます。もちろん音楽ファイルやムービーファイルなどでもOKなのでコマンドラインでは中身が確認しにくいファイルの確認に使えます。

zmv(ビルトインモジュール)

こちらはプラグインマネージャではなくzshにもともと組み込まれているビルトインモジュールです。

ビルトインモジュールはautoloadを使って読み込みます。

以下を設定ファイルに記述します。

autoload -Uz zmv
alias zmv='noglob zmv -W'

autoloadはzmvコマンドが発行されたら自動的にファンクションをロードしてねという命令です。その下のaliasはzmvでは定石なので後述します。

zmvは一括してファイルをリネームするとても便利なモジュールです。例をあげましょう。

zmv *.htm *.html

たったこれだけでカレントディレクトリの.htm拡張子のファイルが.html拡張子にリネームされます。bashを使っていた方ならわかると思いますが、これをbashで実現するにはforループや変数置換を駆使しなければいけないのでなかなか大変です。

実はこのzmv、本当はもっと多機能で一致した文字列を複数回再利用したりも出来るのですが、使い方がやや煩雑なので上記の様にaliasで-Wオプションを指定してだけで置換できるようにするのが定番です。noglobはシェルがそのを勝手に補完しない様にと言う指定です。

まとめ bashからzshへの乗り換えに向けて

ということで駆け足でbashとzshへの移行、bashとzsh違い、そしておすすめのカスタマイズ方法を紹介してきました。

ここはほんのさわりで、その他の膨大なプラグインの利用によりzshの広大な世界への扉が開かれます。

取り急ぎはbashからの移行ポイントとしては起動ファイルの違いとシェル変数の違いを意識しながら設定ファイルを組み、ビルドインの機能から徐々に使ってみれば良いかと思います。

スクリプトについては基本的にbashの制御構造が使えるので互換性も検討してしばらくはbash構文にするのが無難かもしれません。

インタラクティブシェルとしては深い階層や水平移動、自動pushdのcdが非常に使いやすいのでその辺りから積極的に利用すると良いでしょう。

この記事を見た人がよく読んでいる記事

トップページ

節約テクノロジ > bashとzshの違い。bashからの乗り換えで気をつけるべき16の事柄