macOS 10.15 Catalina でターミナルのやつが zsh になった
デフォルトのシェルが bash から zsh というものになったそうだ。
zsh は bash の上位互換だそうで、つまり良きものなのだろう。
zsh を使ってみる
Mojave から Catalina へのアップグレードした場合だと bash がまだ生き残っている。すぐには zsh にならないのだ。
ターミナルを立ち上げると次のようなメッセージが表示される。
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.
よくわかんねーながらもchsh -s /bin/zsh
ってのをやれって書いてある気がするのでやってみる。
と、そこはもう zsh。
読み方もわかんねーような世界に来てしまった。雑種、みたいな発音で大丈夫だろうか(ゼットシェルと読むみたい)。
zsh には入力補完というのがある。
試しにcd
の後ろに半角スペースを入力してからtabキーを押してみる。
tabキーをを押すたびに、カレントディレクトリのなかにある、移動先の候補を補完してくれる。これ、すごい便利だ。
もうひとつcontrol+rキーを押すとイクリメンタル・サーチで過去の履歴を検索できちゃう。
control+rで後方検索、contro+sで前方検索になるとのこと。
これもかっこいい。
ちなみに、ターミナルの「環境設定」>「一般」タブ>「開くシェル」の欄で、bash に戻すこともできる。「コマンド」を選択し、完全パスを/bin/bash
にすれば OK。
bash との違い
そもそも bash との違いはあまりないみたいだ。
筆者が使ってるだいたいのシェルスクリプトが動く、と思う。
ただ、ひとつ大きく違うのは配列の開始番号だ。
zsh の配列は 0 ではなく 1 から始まるらしい。
こちらの、Catalinaでデフォルトシェルが「zsh」に変わる、bashとの違いは? – 新・OS X ハッキング!(241) | マイナビニュースの記事を例文をそのまんまシェルスクリプトにしてみた。
📝 zshtest.sh
#!/bin/bash
array=(apple orange peach)
for i in `seq 0 3`; do
echo "array[$i] = ${array[$i]}"
done;
一行目を#!/bin/bash
にして試してみる。
$ zshtest.sh
array[0] = apple
array[1] = orange
array[2] = peach
array[3] =
array の先頭は 0 番目になっている。
次に一行目を#!/bin/zsh
にしてふたたび実行。
% zshtest.sh
array[0] =
array[1] = apple
array[2] = orange
array[3] = peach
今度は先頭が 1 から開始されている。これは覚えておいたほうがよさげ。
zshrc などの設置
bash_profile とかで書いたものがあれば、それを zsh でもほぼ流用できる。
こういう冴えたやり方が紹介されていた。
cat .bash_profile >> .zprofile
プロンプトの書き方だけは違う。
zsh の設定ファイルは、.zshenv > .zprofile > .zshrc > .zlogin の順番で読みこまれる。違いはよくわからないが、プロンプトの設定は zshrc に書かないと反映されなかった。
筆者はパスは .zshenv に書いた。
export PATH=$PATH:~/bin:/usr/local/opt
zshrc にプロンプトとかエイリアス、関数を書いた。
PROMPT='%K{cyan}%T%k %n@%m %F{yellow}%~%f
%F{green}[>_ ] >%f '
alias zrc='open -a CotEditor ~/.zshrc'
dict(){
open dict://$1
}
プロンプトの書き方は下の記事が詳しい。
参考にさせていただきました。ありがとうございます。
追記 19.12.27
もうひとつ、これは知らなかったんだけど、有名らしい違いがあった。
bash で以下のようなシェルスクリプトを実行する。
今いるディレクトリのファイルやらフォルダやらを、番号をつけて書き出すスクリプトだ。
#!/bin/bash
a=1
ls -1 | while read line
do
echo $a "$line"
a=`expr $a + 1`
done
ls -1 で一行ずつリストを出し、それを line という変数におさめている。a という変数はループが進むにつれて 1 ずつ加算されていく。
実行結果は以下の通り。
なかなかいい感じだ。ふつうに動作しているように見える。
1 Applications
2 Desktop
3 Documents
4 Downloads
5 Library
6 Movies
7 Music
8 Pictures
9 Public
10 bin
このループを終えた後、変数 a の数字はいくつになっているか。一見 10 のように思えるが、echo で出力したあと数字が足されるので答えは 11。これが正解と考えられる。
ところがそうはならないのである。
上記のシェルスクリプトの最後に、一行追加して確認してみる。
#!/bin/bash
a=1
ls -1 | while read line
do
echo $a "$line"
a=`expr $a + 1`
done
echo 'a = '$a
実行結果は以下の通り。
1 Applications
2 Desktop
3 Documents
4 Downloads
5 Library
6 Movies
7 Music
8 Pictures
9 Public
10 bin
a = 1
変数 a は 1 のまま変わらないのだ。
なんでこんなことになるか、よくわからない。while はパイプを通すと、変数をいじれないのだそうで、これはこれで正しい挙動とも聞いたことがある。
なんであれ、while で変数をどうこうしても、while の外には出せないのである。
#!/bin/bash
a=1
str='Documents ないよ'
ls -1 | while read line
do
if test "$line" = 'Documents'; then
str='Documents 見つけた! '`echo $a`'番目に。'
fi
a=`expr $a + 1`
done
echo 'a = '$a
echo 'str = '"$str"
今いるディレクトリの中身を ls でリストにして while で順番に調べ、「Documents」というフォルダなり、ファイルがあるかどうかを調べるスクリプト。
これも実行結果はむなしい。
a = 1
str = Documents ないよ
しょうがないので、かつてはIFS=$'\n'
みたいなことをして for を使っていた。
しかし zsh においては、この構文の挙動が bash と違うのだ。
while の外に、変更した変数を持ち出せるのである。
上記のスクリプトの一行目を#!/bin/bash
から #!/bin/zsh
に書きかえてみる。
再度実行した結果は、
a = 11
str = Documents 見つけた! 3番目に。
ちゃんと想定した通りに動いてくれる。
これは、非常にグッジョブといえるのではないかと思う。
read コマンドが違う
bash でread -p '朝ごはんなに食べた? >' gohan
などとやると、以下のようになる。
$ read -p '朝ごはんなに食べた? >' gohan
朝ごはんなに食べた >目玉焼き
$ echo $gohan
目玉焼き
zsh で同じことをやると怒られてしまう。
% read -p '朝ごはんなに食べた? >' gohan
read: -p: no coprocess
bash と zsh では read のオプションが違うのだ。
上記のコードを zsh でやる場合、以下のように書くらしい。
% read gohan\?'朝ごはんなに食べた? >'
朝ごはんなに食べた >トースト
% echo $gohan
トースト
read 変数\?'プロンプト'
という具合に、はてなをバックスラッシュでエスケープして、プロンプトとなる文字列を指定する。
違いは他にもある。
bash なら-a
で、配列に入力できるようになる。
が、zsh の場合は-A
で配列変数を指定する。
% read -A michibata
karen jessica angelica
% echo $michibata[3]
angelica
bash では-n
で入力文字数を指定する。
zsh では -k
だ。
% read -k 1 yesno
y%
% echo $yesno
y
また zsh には-q
というオプションがある。yes か no かを尋ねる専門のオプションみたいで、y を入力した場合は 0 が返され、n なら 1 が返ってくる。
以下のように使うらしい。
% echo 'てか、朝ごはん食べた?(y/n)'; if read -q;
then
echo 'え、食べたの?'
else
echo '駄目じゃん'
fi
てか、朝ごはん食べた?(y/n)
yえ、食べたの?
下記の記事が詳しい。
🔗 bash, zshでyes/no判定をするワンライナー – Qiita
この部分は 20.01.06 に追記しました。
変数展開みたいなの
f='/Users/username/Pictures/photo.jpg'
みたいな、ファイルパスが入った変数があったとして、ファイル名やファイルの拡張子、あるいはファイルのあるディレクトリを取り出したいとき、よく変数展開と呼ばれる手法を使う。
echo ${f%.*} 拡張子を取り除く photo.jpg > …/Pictures/photo
echo ${f##*.} 拡張子を取り出す photo.jpg > jpg
echo ${f##*/} ファイル名取得 basename /Pictures/photo.jpg > photo.jpg
echo ${f%/*} ディレクトリ取得 dirname /Pictures/photo.jpg > /Pictures
変数の後ろにあるマークが「 # 」なら頭から文字を見ていって、例えば「.*」のようなパターンを探し、見つけるやいなや、頭からその場所までを削除する。「 ## 」という具合に二重になっている場合は、一番最後に見つかったパターンから頭までを削除。
「 ##*. 」なら最後に見つかった「 文字列. 」までが削除。残るのは拡張子だけだ。
変数の後ろにあるマークが「 % 」なら、尻から文字を見ていってパターンを探す。
「 %% 」だと最後に見つかったパターンから右を削除。
これは bash でも zsh でも同様に使える。
しかし zsh の場合はもっと簡単な方法がある。
echo $f:e 拡張子を取り出す /Pictures/photo.jpg > jpg
echo $f:h ディレクトリを取り出す /Pictures/photo.jpg > /Pictures
echo $f:r 拡張子を取り除く /Pictures/photo.jpg > /Pictures/photo
echo $f:t ファイル名を取り出す /Pictures/photo.jpg > photo.jpg
嬉しいことに、このオプションは複数指定できる。
拡張子もパスも必要ない、ファイル名だけが必要な場合は以下のようにすればいい。
echo $f:r:t /Pictures/photo.jpg > photo
オプションは他にもある。
下記の記事が詳しい。勉強させていただきました。ありがとうございます。
この部分は 20.01.06 に加筆しました。
余談だけど変数展開で文字列の置換も出来る。${変数名//検索文字列/置換文字列}
という感じ。
% baka='馬鹿! 馬鹿! 大馬鹿!'
% echo ${baka//馬鹿/好き}
好き! 好き! 大好き!