zsh のあの記号 (チートシート)
シェルやシェルスクリプトを使っていて「この記号なんだこれ」ってなったときに見る記事です。記号について調べたくてもググラビリティが低くて検索できないので、まとめました。正直 man ページ見れば全部分かるんだけどね
筆者は zsh (zsh 5.8) を使っているので基本的に zsh についてまとめていますが、 bash など他のシェルで使用できるものもあります。 POSIX sh では使えないものが結構多いので注意してください (#!/bin/sh って書いているそこのあなたですよ) 。
コマンドの表記法
いきなりシェルとは関係無いことですが、必ず目にするものですのであらかじめ解説します。
よく「このコマンドを実行します」と書いてある記事では、行頭に $ や # が書いてあります。
$ yes 高須クリニック
# dd if=/dev/urandom of=/dev/sda
(これらのコマンドは危険なので、安易に実行しないように。)
$ で始まっているものは一般ユーザーで実行するコマンド、 # で始まっているものは root で実行するコマンドまたはコメントです。 $ や # 自体は入力する必要はありません。ただのプロンプト (入力の促し) です。お使いのシェルの設定によっては % など別の記号が表示されている場合もあります。
コマンドのオプション
これもシェルとは直接関係無いですが、知っておいた方が良いと思うので解説します。
典型的なコマンドにオプションを指定する際、 1 文字のオプションとフルネームのオプションの 2 種類指定方法があります。
1 文字のオプション
1 文字のオプションは - に続いてアルファベットもしくは数字を書きます。
$ docker run -i -t alpine:latest
この場合、 -i オプションと -t オプションを同時に指定したことになります。 1 文字のオプションは、まとめることができます。
$ docker run -it alpine:latest
- に続いて 1 文字のオプションを複数くっつけて書くことができます。 -it は -i -t と同じ意味です。
フルネームのオプション
フルネームのオプションは -- に続いてオプション名を書きます。単語の間は - で区切ります。
$ gpg --full-gen-key --expert
この場合、 --full-gen-key オプションと --expert オプションを同時に指定したことになります。
フルネームのオプションの直後に = をつけて、オプションの引数を指定することがあります。
$ ls --format=single-column
これは、 --format オプションの引数に single-column を指定しています。これは
$ ls --format single-column
と同じ意味です。好きな方を使ってください。
1 文字のオプションとフルネームのオプションは混ぜて使うことができます。フルネームのオプションの短縮形として 1 文字のオプションが用意されている場合もあります。
- で始まるファイル名の指定
- で始まるコマンドライン引数はオプションとして解釈されます。例えば -hoge.txt といったファイルを削除したくても
$ rm -hoge.txt
で削除することはできません。このコマンドは
$ rm -h -o -g -e -. -t -x -t
と解釈されてしまいます (シェルが展開する訳ではありません) 。
コマンドラインでは、 -- と指定した後にスペースをあけると、それより後の文字列はオプションとして解釈されなくなります。 -hoge.txt を削除する場合は
$ rm -- -hoge.txt
と指定すればファイル -hoge.txt を指定できます。 -- 自体は単に無視されます。
例外
上述のオプション指定方法はあくまで慣習なので、コマンドの作りによっては全く違うオプション指定方法の場合があります。 dd コマンドや dig コマンドなどが良い例です。
$ dd if=/dev/random of=rand.bin bs=256 count=1
$ dig +short a blog.livewing.net
各コマンドの使用にあたって man ページをよく読むようにしましょう。
ちなみにコマンドラインオプションの解析には getopt がよく使われているみたいです。
ファイル名の -
またまたシェルとは関係無いですが、シェルを扱うにあたって重要な事項なので解説します。
コマンドでファイル名を指定するべきところで - と指定されている場合があります。これは標準入力または標準出力を表しています。
$ wget -O - https://jsonplaceholder.typicode.com/posts/1
wget コマンドは -O オプションで出力ファイル名を指定することができます。出力ファイル名を - と指定することで出力先を標準出力に設定しています。
これもあくまで慣習です。コマンド側が対応していない場合もあります。
ちなみに筆者は curl 派です。
シンプルコマンドとパイプライン
ようやくシェルの話です。
|
| の前のコマンドの標準出力を、 | の後のコマンドの標準入力に流します。 Unix の醍醐味であるパイプラインです。単にパイプと言うのが一般的です。
$ echo hoge | sed 's/o/a/'
hage
echo hoge は、標準出力に hoge (+改行) と出力するコマンドです。 sed 's/o/a/' は、標準入力の文字列の o を a に置換して標準出力に出力するコマンドです。 echo コマンドの出力をそのまま sed の入力に渡しています。
パイプは繋げて書くことができます。よくあるのが sort と uniq による重複排除です。
$ cat data.txt | sort | uniq
wc コマンドで行数などを数えるのも頻出のパターンです。
$ cat data.txt | sort | uniq | wc -l
|&
|& は 2>&1 | の短縮形です。標準エラー出力を標準出力に流してパイプします。 2>&1 について詳しくはリダイレクトの節で解説します。
;
; で繋がれたコマンドリストを左から順番に実行します。 1 行で複数のコマンドを書く際に使用します。改行と同じ意味です。
$ echo hello; echo world
hello
world
これは以下のコマンドと等価です。
$ echo hello
$ echo world
; の前のコマンドの終了コードに関わらず、順次実行されます。
&&
&& で繋がれたコマンドリストを左から順番に実行します。もしリスト中のコマンドが失敗した (終了コードが 0 以外である) 場合は、それより右のコマンドは実行されません。いわゆる短絡評価 AND 演算子です。
$ grep -i sourpls chat.log && echo found
左側の grep -i sourpls chat.log が成功した (終了コードが 0 である) 場合、右側の echo found が実行されます。
||
|| で繋がれたコマンドリストを左から順番に実行します。もしリスト中のコマンドが成功した (終了コードが 0 である) 場合は、それより右のコマンドは実行されません。いわゆる短絡評価 OR 演算子です。
$ curl https://example.com/ || echo error
左側の curl https://example.com/ が失敗した (終了コードが 0 以外である) 場合、右側の echo error が実行されます。
&
コマンドの末尾に書くと、コマンドをバックグラウンドで起動します。バックグラウンドで起動したコマンドの終了は待機しません。
$ sleep 3m &
[1] 12345
$
バックグラウンドで実行中のコマンドはジョブテーブルに登録されます。
$ jobs
[1] + running sleep 3m
fg コマンドでバックグラウンドからフォアグラウンドに処理を移し、コマンドの終了を待機することができます。また、フォアグラウンドで実行中の状態で Control + Z を押してサスペンドし、 bg コマンドでバックグラウンドで再開することができます。
& は単一のコマンドに作用します。複数のコマンド全体を 1 つのバックグラウンドジョブとして扱うには、後述の { } や ( ) を使用します。
&|, &!
& と同じですが、バックグラウンドで起動したコマンドはジョブテーブルに登録されなくなります。
複合コマンド
制御構文などは複合コマンドとなります。記号が絡むものを紹介します。
for (( ; ; ))
C 言語スタイルの for 文です。括弧は二重に書く必要があります。
$ for (( i = 0; i < 10; i++ )) do echo $i; done
0
1
2
3
4
5
6
7
8
9
case ) ;; ;& ;|
case 直後の値が ) の前に書かれているパターンにマッチした際、 ;; までのコマンドを実行します。最後の ;; は省略可能です。
case $PWD in
/)
echo 'ROOT!'
;;
$HOME)
echo 'HOME!'
;;
*)
echo 'OTHER!'
esac
) の前に書くのはパターンです。 * はすべてにマッチします。どのパターンにもマッチしなかった場合の処理を書くのに使用します。その他 | や [ ] も使用できます。
;; までコマンドが実行されたら、 esac までジャンプします。それ以降のパターンのチェックはしません。 ;; の代わりに ;& を書いた場合、次のパターンの評価をせずに続けて実行します。 ;; の代わりに ;| を書いた場合、次のパターンの評価を行い続行します。
while や shift コマンドと合わせて使うことで、簡易コマンドラインオプションパーサーを書くことができます。
verbosity=0
output=/dev/stdout
while [[ $# -gt 0 ]]; do
case "${1}" in
-v|--verbose)
(( verbosity += 1 ))
;;
-*)
echo "Unrecognized option: ${1}" >&2
exit 1
;;
*)
output="${1}"
;;
esac
shift
done
{ }
{ と } の中を順番に実行します。コマンドのグループ化です。
$ { echo hello; echo world }
& をつけて、複数のコマンド全体をバックグラウンド起動することもできます。ラーメンタイマーなどに使えます。
$ { sleep 3m; toilet --termwidth ラーメンできたぞ | cowsay -n | lolcat } &
また、リダイレクトをまとめることができます。
$ { echo hello; echo world } > message.txt
$ cat message.txt
hello
world
( )
シェルをサブプロセスとして新たに起動し (サブシェル) 、そのサブシェルの中で ( と ) の中を順番に実行します。変数はサブシェルへ引き継がれますが、サブシェル内で変更された変数はサブシェル終了時に破棄されます。
$ ( a=123; echo $a )
123
$ echo $a
$
関数本体をサブシェルとして書くことで、作業ディレクトリを元に戻す処理などが不要になります。
build() ( cd src && make )
{ } と同様、バックグラウンドで起動したりリダイレクトをまとめることができます。
コメント
インタラクティブでないシェル (シェルスクリプトなど) の場合は、コメントを書くことができます。
#
インタラクティブでないシェルでは、 # から改行までの文字列が無視されます。シェルスクリプトのコメントを書く際に使用します。
:(){ :|:& };: # fork 爆弾でシステムを破壊 (絶対に実行してはいけない)
エイリアス
alias を使用することで、コマンドに別名をつけることができます。
\
エイリアスに \ を前置すると、それをエイリアスとして扱わなくなります。
例として、 ls コマンドにエイリアスを割り当てている場合を考えます。
$ which ls
ls: aliased to ls --color=auto
ls を実行すると、実際に実行されるのは ls --color=auto となります。このエイリアスを一時的に展開しないようにするには、 \ls として実行します。
$ \ls
ls が --color=auto 無しで実行されます。
alias vim=emacs や alias emacs=vim のような宗教戦争を引き起こすイタズラを回避するのにお役立てください。
エスケープ
特定の文字に \ を前置することで、特殊な文字を表現できます。
\ + 改行
行末の \ + 改行は無視されます。長い 1 行のコマンドの途中で見やすくするため改行したい場合に使用します。
$ docker run -v /my-volumes/documents:/root/documents \
--rm -it alpine:latest /bin/sh
' '
' で囲まれた文字列は、ほとんどそのままの文字列として扱われます。改行もそのままです。シェル展開が行われません。 \' はエスケープシーケンスとして扱われません。
$ echo 'hello\nworld'
hello
world
$ echo 'foo
quote> bar'
foo
bar
$ echo 'PWD is $PWD'
PWD is $PWD
文字列は繋げて書くことで連結できます。
$ echo 'hello''world'
helloworld
$ echo 'foo'bar
foobar
bash などではすべてのエスケープシーケンスが解釈されず、 \ がそのままの文字として扱われます。
$' '
$' と ' で囲まれた文字列 (' で囲まれた文字列に $ を前置したもの) は、 \' のエスケープシーケンスが使用できます。
$ echo $'\' <- This is apostrophe'
' <- This is apostrophe
bash などではすべてのエスケープシーケンスが解釈されるようになります。
" "
" で囲まれた文字列は、パラメータ展開・コマンド置換・ヒストリー展開が起こり、それらが評価された状態の文字列となります。エスケープシーケンスが使用できます。
$ echo "PWD is $PWD"
PWD is /home/user
$ echo "PWD is $(pwd)"
PWD is /home/user
$ echo "PWD is `pwd`"
PWD is /home/user
$ echo "!!"
echo "echo "PWD is `pwd`""
echo PWD is /home/user
追加のエスケープシーケンスは以下の 5 文字が使用可能です。
$ echo "\\ \` \" \$ \!"
\ ` " $ !
リダイレクト
リダイレクトの話の前に、ファイル記述子 (ファイルディスクリプタ、 FD: File Descriptor) について解説します。
プログラムの標準入力・標準出力・標準エラー出力は、ファイル記述子という ID が割り当てられています。標準出力が 0 、標準出力が 1 、標準エラー出力が 2 となっています。
プログラムがファイルを open すると 3 以降が順番に割り当てられます。シェルにおいてファイル記述子を割り当てるなどの操作を行うには、シェル組み込みの exec コマンドを使用します。
$ exec 3>outfile.txt
この例では、 outfile.txt を上書きモードで開いてファイル記述子 3 に割り当てています。
ファイル記述子は /proc/[PID]/fd を ls すると確認することができます。
$ ls -l /proc/$$/fd
合計 0
lrwx------ 1 user group 64 0 -> /dev/pts/1
lrwx------ 1 user group 64 1 -> /dev/pts/1
lrwx------ 1 user group 64 2 -> /dev/pts/1
デフォルトでは、標準入出力すべてが /dev/pts/1 という仮想端末を指しています (環境によって異なります) 。このファイル記述子が指している先を変更する機能がリダイレクトです。
以下の < や > などは、 2> のように直前にファイル記述子を表す番号を指定することができます。
<
ファイルを読み込み、それを標準入力 (0) へ渡します。 < は 0< の短縮形です。
$ gpg --import < public-key.asc
<>
ファイルを読み書きできる状態で開き、標準入力 (0) に割り当てます。 <> は 0<> の短縮形です。
新しいファイル記述子を割り当て、 /dev/tcp/* など特殊なファイルを使用して通信をする例が多いようです。
$ exec 3<>/dev/tcp/example.com/80
$ echo -e "GET / HTTP/1.1\n\n" >&3
$ cat <&3
この例ではファイル記述子 3 を作成しています。これとリダイレクトを活用して TCP 通信を行っています。
>
標準出力 (1) をファイルへ書き込みます。 > は 1> の短縮形です。標準エラー出力をリダイレクトするには、 2> のようにファイル記述子を明示的に指定します。
$ gpg --export > public-key.asc
リダイレクト先のファイルが既に存在していた場合、ファイルの長さが 0 に切り詰められてから出力されます (上書き) 。 シェルで CLOBBER オプションが外されている場合は、上書きしようとするとエラーが発生します。
>|, >!
> と同じですが、 CLOBBER オプションに関わらず上書きします。
>>
標準出力 (1) をファイルへ追記します。 >> は 1>> の短縮形です。
$ echo hello >> greeting
$ echo world >> greeting
リダイレクト先のファイルが存在せず、 CLOBBER と APPEND_CREATE オプションが外されている場合は、エラーが発生します。
>>|, >>!
>> と同じですが、 CLOBBER や APPEND_CREATE オプションに関わらずファイルを新規作成します。
<<
標準入力 (0) の内容をコマンド内に直接記述することができます。<< は 0<< の短縮形です。 << の直後の文字列が現れるまでを入力とします。一般的にヒアドキュメント (here-document, heredoc) と言われます。
$ sed 's/^http:/https:/' <<EOF
heredoc> http://livewing.net/
heredoc> http://blog.livewing.net/zsh-symbols
heredoc> EOF
https://livewing.net/
https://blog.livewing.net/zsh-symbols
<<EOF とした場合、 EOF という行が現れるまでの文字列を標準入力へ渡します。 EOF は End-Of-File の略で、よく使用されます。他の文字列も使用可能です。
ヒアドキュメントはデフォルトでシェル展開が行われます。シェル展開を無効にするには、終端文字列指定を ' か " で囲むか、 \ を前置します。
$ cat <<"EOF"
heredoc> WORKDIR $PWD
heredoc> EOF
WORKDIR $PWD
<<-
<< と同じですが、各行の先頭のタブ文字が無視されるようになります。スペースは保持されます。
<<<
<<< の右側をシェル展開し、標準入力 (0) へ渡します。 <<< は 0<<< の短縮形です。ヒアストリング (here-string) と言われます。
$ cat <<<"EDITOR is $EDITOR"
EDITOR is nvim
これは以下のコマンドと等価です。
$ echo "EDITOR is $EDITOR" | cat
<&, >&
ファイル記述子を複製します。<& や >& の後のファイル記述子を複製し、 <& や >& の前の番号をつけます。 <& は 0<& の短縮形、 >& は 1>& の短縮形です。よく >&2 や 2>&1 とかで見る奴ですね。
>&2 (1>&2) はファイル記述子 2 を 1 へ複製します。つまり、標準エラー出力を表すファイル記述子を複製し、標準出力にセットします。標準出力に出力されたものはすべて標準エラー出力へ流されます。
$ echo stderr >&2
2>&1 はファイル記述子 1 を 2 へ複製します。つまり、標準出力を表すファイル記述子を複製し、標準エラー出力にセットします。標準エラー出力に出力されたものはすべて標準出力へ流されます。
$ curl https://example.com/ > /dev/null 2>&1
この例のように、標準出力と標準エラー出力の両方を /dev/null へ捨てるといったことをよくすると思いますが、リダイレクトを書く順番に注意してください。リダイレクトは左から順番に評価されるため、> /dev/null と 2>&1 を逆順にすると想定通りに動きません。 2>&1 を先に評価するとなると、ファイル記述子 1 が指しているのは標準出力になり、それが複製されます。ファイル記述子 1 のバックアップを 2 に作ったイメージです。その後 > /dev/null によってファイル記述子 1 を変更するので、結果としては「標準エラー出力の内容が標準出力に出力される」ことになります。
<&-, >&-
ファイル記述子のファイルを close します。自分で作成したファイル記述子を閉じる際に使用します。 <&- は 0<&- の短縮形、 >&- は 1>&- の短縮形です。
$ exec 3>&-
<&p, >&p
既に起動しているコプロセスと標準入出力をやり取りします。 <&p はコプロセスの標準出力を受け取ります。 >&p はコプロセスの標準入力へ渡します。
$ coproc tr a-z A-Z
$ cat <&p &
$ echo hoge >&p
$ coproc exit
HOGE
この例では、コプロセス tr a-z A-Z を起動し、コプロセスの出力をバックグラウンドの cat で受け取り、 echo hoge でコプロセスの標準入力へ出力しています。データが少ないとバッファされたまま返ってこないので、コプロセスを終了し flush しています。すると、 tr で処理された内容が cat によって出力されます。
コプロセスの使い方はシェルによって大きく異なります。
>&, &>
標準出力と標準エラー出力の両方をまとめて同一のファイルへ上書きします。 >& や &> の後にファイル名を指定します (ファイル記述子の数字ではありません) 。
$ curl https://example.com/ &> /dev/null
>& は先述の「ファイル記述子の複製」と記号が同じであり、曖昧さがあるため、こちらの用途の場合は &> を使用することをおすすめします。
>&|, >&!, &>|, &>!
>& ・ &> と、 >| ・ >! の組み合わせです。
>>&, &>>
標準出力と標準エラー出力の両方をまとめて同一のファイルへ追記します。
>>&|, >>&!, &>>|, &>>!
>>& ・ &>> と、 >>| ・ >>! の組み合わせです。
{ }>, >&$ など
リダイレクト記号の前に、変数名を { } で囲って前置すると、ファイルを開きつつ変数にファイル記述子の番号を代入します。
$ exec {myfd}>~/myfile.txt
$ echo $myfd
11
これで、 ~/myfile.txt を上書きモードで開き、そのファイル記述子を変数 $myfd に代入します。この例では 11 が自動的に割り当てられました。
このファイル記述子を使用するには、リダイレクト記号の後に $変数名 と指定します (単純に展開されます) 。
$ echo hello >&$myfd
これで、ファイル記述子 11 を通して hello という文字列を書き込みました。
ファイル記述子を使用し終わったら、 >&- などを使用して閉じます。
$ exec {myfd}>&-
$ cat ~/myfile.txt
hello
今回は > を使用してファイルを上書きモードで開きましたが、 >> や < 、 <> など他のリダイレクトも使用することができます。
コマンド無しの <
コマンド無しで < を使用した場合、変数 $NULLCMD と $READNULLCMD を使用して、ファイルの内容を標準出力に出力します。
$ < myfile.txt
これは以下のように解釈されます。
$ $NULLCMD myfile.txt | $READNULLCMD
$NULLCMD のデフォルト値は cat 、 $READNULLCMD のデフォルト値は more です。デフォルト値で展開すると以下のようになります。
$ cat myfile.txt | more
スクリプトの実行
実行しようとしているファイルが実行可能バイナリではない場合、 /bin/sh をインタプリタとして起動し、シェルスクリプトとして実行します。
#!
実行しようとしているスクリプトファイルの 1 行目が #! で始まっている場合、それ以降の文字列をインタプリタのコマンドとして解釈します。 shebang と言われます。
#!/usr/bin/env zsh
echo 'hello from zsh'
これを実行した場合、 /usr/bin/env zsh をインタプリタとして起動し、スクリプトが実行されます。 shebang のパスは絶対パスを指定する必要があります。
$ ./hello.zsh
hello from zsh
Perl や Python 、 Ruby など # 以降をコメントとして扱う言語でも shebang は使えます。 JavaScript は # 以降をコメントとして扱いませんが、 Node.js や Deno では shebang が特別に解釈されるようになっています。
関数
コマンド列を関数として定義し、後で繰り返し呼び出すことができます。
()
関数名の後に () を書くことで関数を定義できます。
$ greeting() echo hello
$ greeting
hello
この例では関数 greeting を定義し呼び出しています。呼び出す際は () は必要ありません。
$1 などの変数で引数にアクセスできます。
$ print_first_arg() echo $1
$ print_first_arg world
world
引数などの特殊な変数については後述します。
匿名関数
関数名を書かないようにするか、 function キーワードを使用することで、匿名関数を作成することができます。匿名関数の宣言直後に引数を指定して、即時実行することができます。
$ () { echo $1 } foo
foo
ジョブ
コマンドに & を後置するなどしてバックグラウンド起動したジョブを管理できます。シェル組み込みの kill コマンドや fg コマンドでジョブを指定できます。
%
jobs コマンドでジョブ一覧が表示されます。
$ jobs
[1] + running sleep 3m
[1] のジョブを kill するには、ジョブ番号を %1 のように指定します。
$ kill %1
[1] + terminated sleep 3m
% に続いてコマンドの先頭部分を指定することもできます。
$ kill %sleep
[1] + terminated sleep 3m
%?
% に続いて文字列を指定すると、コマンドの前方一致でジョブを指定することができますが、 %? に続いて文字列を指定すると、コマンドの部分一致でジョブを指定することができます。
$ kill %?ee
[1] + terminated sleep 3m
%%, %+
最後に起動したジョブを表します。 jobs コマンドで + 記号が付いているジョブを表します。
%-
%% や %+ の前のジョブを表します。 jobs コマンドで - 記号が付いているジョブを表します。
数式の評価
数式の計算を行うには、シェル外部の expr コマンドもしくはシェル組み込みの let コマンドなどを使用できます。シェル組み込みの方が処理速度が速いです。
(( ))
(( と )) で囲まれた文字列は、シェル組み込みの let コマンドの呼び出しとなります。エスケープが不要となるため let より (( )) が推奨されます。
$ (( myvalue = 123 + 456 ))
この文は以下のコマンドと等価です。
$ let "myvalue = 123 + 456"
これを実行すると、変数 $myvalue に 123 + 456 の計算結果が代入されます。
$ echo $myvalue
579
計算結果が 0 の場合、終了コードは 1 となり、それ以外の値の場合、終了コードは 0 となります。エラーが発生した場合、終了コードは 2 となります。
$ (( 0 )); echo $?
1
$ (( 42 )); echo $?
0
$ (( 0 / 0 )); echo $?
zsh: division by zero
2
$(( ))
(( と )) で囲まれた数式を計算し、計算結果を展開します。
$ echo "1 + 2 + 3 = $(( 1 + 2 + 3 ))"
1 + 2 + 3 = 6
$ echo $(( ans = 42 ))
42
$ echo $ans
42
#
数式中に 基数#数値 というフォーマットで、任意の基数で数値を表記できます。
例えば 16 進数で ff と表したい場合は以下のように表記します。
$ echo $(( 16#ff ))
255
なお、 16 進数では 0x 、 2 進数では 0b を前置するなどお馴染みの表記も使えます。また、数値を見やすくするために途中で _ を複数回挟むこともできます。
$ echo $(( 0xffff_ffff ))
4294967295
[# ]
基数を [# と ] で囲んで数式中に前置すると、数式の評価結果が 基数#数値 というフォーマットで出力されます。
$ echo $(( [#8] 16 ))
8#20
$ echo $(( [#16] 65535 ))
16#FFFF
10 進数で 16 は 8 進数で 20 となるため、 8#20 と出力されます。また、 10 進数で 65535 は 16 進数で FFFF となるため、 16#FFFF と出力されます。
# を 2 つ連続で書くと、基数部分を出力しません。
$ echo $(( [##16] 255 ))
FF
C_BASES オプションを有効にすると、 16 進数の出力指定の場合、 0x を前置した形式で出力されます。
$ setopt cbases
$ echo $(( [#16] 65535 ))
0xFFFF
基数指定の後に _ に続いて桁数を後置すると、 _ で桁を区切った形式で出力されます。また、基数や桁数は省略可能で、基数を省略した場合は 10 進数が指定されたものとみなされ、桁数を省略した場合は 3 桁で区切られます。片方だけ指定することもできます。
$ echo $(( [#16_4] 65535 << 8 ))
16#FFFF_0000
$ echo $(( [#_] 1234567890 ))
1_234_567_890
演算子
基本的に C 言語と同じです。
単項演算子
+, -, !, ~, ++, --
++, -- はそれぞれ前置と後置の 2 種類ずつあります。動作は C 言語と同じです。
二項演算子
<<, >>, &, ^, |, **, *, /, %, +, -, <, >, <=, >=, ==, !=, &&, ||, ^^, =, +=, -=, *=, /=, %=, ^=, |=, <<=, >>=, &&=, ||=, ^^=, **=, ,
** は累乗、 ^^ は論理 XOR です。追加の代入演算子 &&=, ||=, ^^=, **= が利用できます。
&&, ||, &&=, ||= は短絡評価です。
三項演算子
? :
( )
演算子の結合順を変更するために ( ) を使用できます。
単項演算子と累乗演算子の結合が通常と異なる点に注意してください。 -3**2 は -9 ではなく 9 と評価されます。これは他のシェルとの互換性を保つためです。 -(3**2) のように、必要に応じて ( ) を使用しましょう。
算術関数
zsh/mathfunc を zmodload すると、数式中で算術関数を使用できます。算術関数は ( ) で呼び出します。
$ zmodload zsh/mathfunc
$ echo $(( sqrt(2) ))
1.4142135623730951
文字の前の ##
数式中に ## に続いて 1 文字書くと、その文字のコードポイントを取得できます。
$ echo $(( ##A ))
65
$ echo $(( [#16] ##A ))
16#41
$ echo $(( [#16] ##あ ))
16#3042
$ echo $(( [#16] ##🍣 ))
16#1F363
## の代わりに #\ も使用できますが、非推奨です。
変数名の前の #
数式中に # に続いて変数名を書くと、その変数に格納されている文字列の 1 文字目のコードポイントを取得できます。
$ mystr=ABCDEFG
$ echo $(( [#16] #mystr ))
16#41
変数の使用
数式中で変数や配列を参照する場合、 $ を前置する必要はありません。
$ ans=42
$ echo $(( ans * 2 ))
84
$ myexpr="1 + 2"
$ echo $(( myexpr * 2 ))
6
$ を前置した場合、数式が評価される前に展開が行われます。
$ myexpr="1 + 2"
$ echo $(( $myexpr * 2 ))
5
この例では、最初に $myexpr が展開され $(( 1 + 2 * 2 )) となります。その後、数式が評価され 5 となります。 C 言語の define と同じく、演算子の結合順に注意する必要があります。
条件式
if などで条件式を記述する際、シェル組み込みの test コマンドなどを使用して条件式を評価します。
[ ]
[ と ] で囲まれた文字列は、シェル組み込みの test コマンドの呼び出しとなります。 [ の実態はコマンドであるため、 [ の直後はスペースを空ける必要があります。
$ ans=42
$ [ "$ans" = 42 ]; echo $?
0
これは以下のコマンドと等価です。
$ ans=42
$ test "$ans" = 42; echo $?
0
条件式を評価した結果、真の場合は 0 、偽の場合は 1 を終了コードとして返します。
$ などのシェル展開が行われてから式の評価が行われるため、 " で囲まれていない場合は展開によって式が変化する場合があります。後述の [[ が使用可能なら、 [[ を使用することを推奨します。
[[ ]]
[[ と ]] で囲まれたものは、条件式として評価されます。 [[ の実態は複合コマンドであるため、 [[ の直後はスペースを空ける必要があります。
$ [[ -a /etc/passwd ]] && echo exist
exist
[[ は [ より、指定できる条件の種類が多く、シェル展開結果にスペースが含まれていても " で囲む必要が無いなどの特徴があります。
-a など
-a file などで右辺の文字列を評価できます。 -a は右辺のファイルが存在するかの確認、 -v は右辺の変数名がセットされているかどうかの確認などたくさんあります。また、 expr1 -gt expr2 や expr1 -eq expr2 など式を 2 つ指定し数値の比較をするものもあります。詳細は man ページをご覧ください。
=, ==
左辺の文字列が右辺のパターンにマッチしているかどうかを評価します。パターン中に ? や * などを使用することができます (これらはただのパターンであり、ファイル名生成は行われません) 。
$ [[ "hello, world" == he*ld ]]; echo $?
0
!=
左辺の文字列が右辺のパターンにマッチしていないかどうかを評価します。 = や == の評価結果を反転します。
=~
左辺の文字列が右辺の正規表現にマッチしているかどうかを評価します。
$ [[ FizzBuzz =~ ^(Fizz|Buzz)+$ ]]; echo $?
0
<, >
左辺と右辺の文字列が辞書順に並んでいるかどうかを評価します。
$ [[ a < b ]]; echo $?
0
$ [[ b > a ]]; echo $?
0
$ [[ 11 < 2 ]]; echo $?
0
あくまで文字列同士の比較であるため、 11 < 2 の両辺はそれぞれ文字列として扱われ、真と評価されます。数値として比較するには -eq -ne -lt -gt -le -ge を使用する必要があります。
( )
式をグループ化します。
!
式に ! を前置すると、式の真偽を反転します。
&&, ||
それぞれ論理積と論理和です。 && の方が結合の優先順位が高いため、必要に応じて ( ) で式をグループ化し、結合順を変更します。
ファイル名生成
条件式中で * などによるファイル名生成は行われませんが、 EXTENDED_GLOB オプションが有効の状態で、文字列の最後で (#q) などのグロブフラグを使用することでファイル名生成を行うことができます。
$ [[ -n D*(#qN) ]]; echo $?
-n は右辺が空ではない文字列の場合、真となります。 D*(#qN) でファイル名生成を行っています。 (#qN) でグロブフラグを設定しています。 N で NULL_GLOB を有効化し、マッチするものが無い場合でも * を残さないように設定しています。 [[ -n D*(#qN) ]] は結果として、 D から始まるファイル・ディレクトリがあるかどうかを評価することになります。
ヒストリー展開
! で始まるものはヒストリー展開です。コマンド入力履歴に関する展開を行います。
イベント指定子
! で始まるイベント指定子で、ヒストリー 1 件を指定することができます。
!!
前回のコマンドを参照します。単に !! を実行すると、前回のコマンドをそっくりそのまま実行することになります。ヒストリー展開を使用した場合、コマンド実行前に展開結果の確認表示が行われます。
$ echo hello
hello
$ !!
echo hello
hello
ただの展開なので、コマンドの任意の部分で使用することもできます。これは他の展開でも同様です。
$ pwd
/home/user
$ echo !!
echo pwd
pwd
!n
最古から数えて n 番目に実行されたコマンドを展開します。 1 スタートで、 1 が一番古いコマンドになります。
$ echo "My first command is !1"
echo "My first command is ls"
My first command is ls
!-n
最新から数えて n 番目に実行されたコマンドを展開します。 1 スタートで、 1 が一番新しいコマンドになります。つまり、 !-1 は !! と等価です。
$ echo "My latest command is !-1"
echo "My latest command is git push -u origin main"
My latest command is git push -u origin main
!
! に続いて文字列を指定すると、その文字列で始まるコマンドを最新のものから検索し、最初にマッチしたものを展開します。
$ echo "My latest sudo command is !sudo"
echo "My latest sudo command is sudo shutdown -h now"
My latest sudo command is sudo shutdown -h now
!?
!? に続いて文字列を指定すると、その文字列に部分一致するコマンドを最新のものから検索し、最初にマッチしたものを展開します。
$ echo "My latest command contains react is !?react"
echo "My latest command contains react is npm i react react-dom"
My latest command contains react is npm i react react-dom
!#
現在のコマンドラインを展開します。
$ echo hello !#
echo hello echo hello
hello echo hello
ワード指定子
イベント指定子に続いて : で区切った後、ワード指定子を指定することでコマンドの一部を抽出することができます。記号のワード指定子を使用する場合、区切り文字の : は省略可能です。
0
コマンドのプログラム名を参照します。 $0 に相当する部分です。
$ echo foo bar baz
foo bar baz
$ echo !!:0
echo echo
echo
数字
コマンドの n 番目の引数を参照します。
$ echo foo bar baz
foo bar baz
$ echo !!:2
echo bar
bar
^
コマンドの最初の引数を参照します。 1 と同じです。
$
コマンドの最後の引数を参照します。
%
最新の !? によるヒストリー参照で部分一致した部分を参照します。 !% !:% !?str?:% と指定する場合のみ利用可能です。
$ echo foo bar baz
foo bar baz
$ echo !?z
echo echo foo bar baz
echo foo bar baz
$ echo !%
echo baz
baz
-
引数を x-y の形式で範囲指定して参照します。開始番号は省略可能で、省略した場合は 0 が指定されます。
$ echo foo bar baz
foo bar baz
$ echo !!:0-2
echo echo foo bar
echo foo bar
*
コマンドのプログラム名以外すべてを参照します。
$ echo foo bar baz
foo bar baz
$ echo !!:*
echo foo bar baz
foo bar baz
数字 *
数字 n に * を後置して n* とすると、 n-$ と同じ意味になります。
数字 -
数字 n に - を後置して n- とすると、 n-$ から最後のものを取り除いたものを参照します。
編集子
更に : を書いた後に、ヒストリーの文字列に対して編集を行います。
$ echo hoge fuga
hoge fuga
$ echo !!:*:s/o/a/
echo hage fuga
hage fuga
s を指定して文字列の置換を行っています。編集子は他にもたくさんあります。詳しくは man ページをご覧ください。
編集子はパラメータ展開やファイル名生成でも使用できます。
プロセス置換
コマンドリストを実行し、そのコマンドリストの標準入出力を無名パイプファイルのパスに置換する機能です。
<( )
<( ) で囲まれたコマンドリストを実行し、その標準出力を特殊なファイルパスとして置換します。
まずはプロセス置換でどのようなファイルパスが取得できるのか確認してみましょう。 <(echo hello) の部分がプロセス置換です。
$ echo <(echo hello)
/proc/self/fd/11
一時的に生成された /proc/self/fd/11 というパスになりました。このファイルはまさにファイル記述子 (11) を表しています。これはシンボリックリンクで、指す先は無名パイプファイルです。 <(echo hello) の標準出力を表しています。
$ ls -l <(echo hello)
lr-x------ 1 user group 64 /proc/self/fd/11 -> 'pipe:[xxxxx]'
パーミッションが読み取り専用 (500) になっています。このファイルを読み込むことで、 echo hello の標準出力を読み込むことができます。
$ cat <(echo hello)
hello
プロセス置換のよくある例として、 2 つのファイルの差分を表示する diff コマンドでの使用があります。比較するファイルのパスを 2 つ指定するのですが、データを加工するなどでパイプを使用する場合、標準入力で受け取るのは片方のみしかできません。もう片方は一時ファイルを作成して読み込むことになります。
$ cat data1.txt | sort | uniq > tmp.txt
$ cat data2.txt | sort | uniq | diff tmp.txt -
プロセス置換を用いれば、一時ファイルを一切作ることなく diff を実行できます。
$ diff <(cat data1.txt | sort | uniq) <(cat data2.txt | sort | uniq)
2 つのプロセスリストの標準出力が、一時的な無名パイプファイルに割り当てられます。 diff はそれらのパイプファイルを読むことで処理を行っています。
2 つのディレクトリ内のファイル一覧の差分を取りたいときにも活躍します。
$ diff <(ls -l src-old/) <(ls -l src-new/)
>( )
>( ) で囲まれたコマンドリストを実行し、その標準入力を特殊なファイルパスとして置換します。
>( ) の使い道として、あるコマンドの標準出力とは別に、標準エラー出力を加工して処理するといったことができます。
$ ./run.sh > app.log 2> >(grep -i 'fatal' > fatal.log)
この例では、 ./run.sh の標準出力を app.log に書き込んでいます。標準エラー出力は、 fatal という文字列がある行のみを抽出して fatal.log に書き込んでいます。 >( ) もファイルパスに置換されるので、 2> などのリダイレクト記号を使用して自由に制御できます。
=( )
<( ) と同様の動きをしますが、プロセスの標準出力の内容が無名パイプとして扱われず、 /tmp ディレクトリ内に一時ファイルを生成し、プロセスの標準出力が普通のファイルへ出力されます。先程の diff の例で一時ファイルの作成をしていましたが、あれを自動化したような感じです。
$ echo =(echo hello)
/tmp/zshXXXXXX
無名パイプの場合、データはストリーミングされるためシークができませんが、普通のファイルの場合は lseek によるシークが可能です。シークが必要な場合は =( ) を使用することになるでしょう。シークが必要ないなら <( ) の方が効率が良いです。コマンドの実行が終了したら、一時ファイルは自動的に削除されます。
<<( )
<<( ) は < と <( ) の組み合わせです。特殊な構文に見えるかもしれませんが、ただリダイレクトとプロセス置換がくっついているだけです。
パラメータ展開
$ でパラメータ (変数) を参照し、展開します。かなり高機能でびっくりします。
$name, ${name}
変数 name を展開します。 { } は、文字列が隣接している際に変数名を区切るために必要になります。
$ myvar=hello
$ echo $myvar
hello
$ echo ${myvar}world
helloworld
${+name}
変数 name が定義されているなら 1 、定義されていなければ 0 に置換されます。
$ myvar=hello
$ echo $+myvar
1
$ hogevar=""
$ echo $+hogevar
1
$ echo $+fugavar
0
${name-word}
変数 name が定義されているなら変数 name の値、定義されていなければ word に置換されます。
$ myvar=hello
$ echo ${myvar-world}
hello
$ echo ${hogevar-world}
world
${name:-word}
変数 name が定義されており、空でないなら変数 name の値、それ以外の場合は word に置換されます。
$ myvar=hello
$ echo ${myvar:-world}
hello
$ hogevar=""
$ echo ${hogevar:-world}
world
${name+word}
変数 name が定義されているなら word に置換され、それ以外の場合は空です。
$ myvar=hello
$ echo ${myvar+world}
world
$ echo ${hogevar+world}
${name:+word}
変数 name が定義されており、空でないなら word に置換され、それ以外の場合は空です。
$ myvar=hello
$ echo ${myvar:+world}
world
$ echo ${hogevar:+world}
${name=word}
変数 name が定義されているなら変数 name の値、定義されていなければ、値を word として変数 name を定義し置換します。
$ myvar=hello
$ echo ${myvar=world}
hello
$ echo ${hogevar=world}
world
$ echo $hogevar
world
${name:=word}
変数 name が定義されており、空でないなら変数 name の値、それ以外の場合は値を word として変数 name を定義し置換します。
$ myvar=hello
$ echo ${myvar:=world}
hello
$ hogevar=""
$ echo ${hogevar:=world}
world
$ echo $hogevar
world
${name::=word}
変数 name が定義されているかどうかに関わらず、値を word として変数 name を定義し置換します。
$ myvar=hello
$ echo ${myvar::=world}
world
$ echo $myvar
world
${name?word}
変数 name が定義されているなら変数 name の値に置換し、定義されていなければ word を print してシェルを終了します。インタラクティブシェルの場合は、エラーメッセージが表示されます。
$ myvar=hello
$ echo ${myvar?world}
hello
$ echo ${hogevar?world}
zsh: hogevar: world
${name:?word}
変数 name が定義されており、空でないなら変数 name の値に置換し、定義されていなければ word を print してシェルを終了します。インタラクティブシェルの場合は、エラーメッセージが表示されます。
$ myvar=hello
$ echo ${myvar:?world}
hello
$ hogevar=""
$ echo ${hogevar:?world}
zsh: hogevar: world
${name#pattern}, ${name##pattern}
変数 name の先頭がパターン pattern にマッチした場合、マッチした部分を削除した状態で置換されます。マッチしなかった場合は変数 name の値にそのまま置換されます。
# の場合は最短マッチ、 ## の場合は最長マッチです。
$ myvar=hello
$ echo ${myvar#hel}
lo
$ echo ${myvar#wtf}
hello
${name%pattern}, ${name%%pattern}
変数 name の末尾がパターン pattern にマッチした場合、マッチした部分を削除した状態で置換されます。マッチしなかった場合は変数 name の値にそのまま置換されます。
% の場合は最短マッチ、 %% の場合は最長マッチです。
$ myvar=hello
$ echo ${myvar%lo}
hel
$ echo ${myvar%wtf}
hello
${name:#pattern}
変数 name がパターン pattern にマッチした場合、空白の文字列で置換されます。マッチしなかった場合は変数 name の値にそのまま置換されます。
変数 name が配列の場合、パターン pattern にマッチした要素がすべて取り除かれた状態で置換されます。
$ myvar=hello
$ echo ${myvar:#h???o}
$ echo ${myvar:#hell}
hello
$ myarray=(hello world hello wtf)
$ echo ${myarray:#hello}
world wtf
${name:|arrayname}
変数 name が配列 arrayname のいずれかの要素と等しい場合、空白の文字列で置換されます。
変数 name が配列の場合、配列 name から配列 arrayname のいずれかの要素にマッチした要素がすべて取り除かれた状態で置換されます。
$ myvar1=hello; myvar2=hell
$ myarray1=(hello world hello wtf); myarray2=(hello world)
$ echo ${myvar1:|myarray2}
$ echo ${myvar2:|myarray2}
hell
$ echo ${myarray1:|myarray2}
wtf
${name:*arrayname}
変数 name が配列 arrayname のいずれかの要素と等しい場合、変数 name の値に置換され、それ以外の場合は空白の文字列で置換されます。
変数 name が配列の場合、配列 name から配列 arrayname のいずれかの要素にマッチしない要素がすべて取り除かれた状態で置換されます。
${name:|arrayname} と逆の動作をします。
$ myvar1=hello; myvar2=hell
$ myarray1=(hello world hello wtf); myarray2=(hello world)
$ echo ${myvar1:*myarray2}
hello
$ echo ${myvar2:*myarray2}
$ echo ${myarray1:*myarray2}
hello world hello
${name:^arrayname}, ${name:^^arrayname}
配列 name と配列 arrayname を zip して flat します。 ^ の場合、短い方の配列に長さを揃えて zip します。 ^^ の場合、短い方の配列を長い方の配列に長さを揃えて zip します (足りない要素は最初から繰り返されます) 。
name が配列でない場合、要素数が 1 である配列とみなされます。
$ myvar=x
$ myarray1=(1 2 3 4); myarray2=(a b)
$ echo ${myarray1:^myarray2}
1 a 2 b
$ echo ${myarray1:^^myarray2}
1 a 2 b 3 a 4 b
$ echo ${myvar:^myarray2}
x a x b
${name:offset}
配列 name の先頭から offset 個取り除いたもので置換します。 name が文字列の場合、文字列を文字の配列とみなします。 offset は数式が展開されます。
$ myarray=(foo bar baz)
$ echo ${myarray:1}
bar baz
$ myvar=helloworld
$ echo ${myvar:2+3}
world
${name:offset:length}
${name:offset} の処理後、先頭から length 個取り出したもので置換します。 length でも数式が展開されます。
$ myarray=(foo bar baz hoge piyo fuga)
$ echo ${myarray:1:3}
bar baz hoge
$ myvar=helloworld
$ echo ${myvar:2*2:1+2}
owo
${name/pattern/repl}
変数 name のうち、パターン pattern に最初にマッチした部分を repl に置き換えます。パターンの先頭に # を書くと先頭に、 % を書くと末尾にマッチします。
$ myvar1=hogehoge
$ echo ${myvar1/o/a}
hagehoge
$ myvar2=helloworld
$ echo ${myvar2/%?????/john}
hellojohn
${name//pattern/repl}
pattern が複数回マッチし、 repl にすべて置き換えます。
${name:/pattern/repl}
文字列全体がパターン pattern にマッチした場合、 repl に置き換えます。
${#spec}
spec の長さで置換します。
$ myvar=hello
$ echo $#myvar
5
$ myarray=(foo bar baz hoge piyo fuga)
$ echo $#myarray
6
${^spec}
RC_EXPAND_PARAM オプションを一時的にオンにします。 ^^ とするとオフにします。
$ myarray=(foo bar baz)
$ echo aaa${^myarray}bbb
aaafoobbb aaabarbbb aaabazbbb
$ echo aaa${^^myarray}bbb
aaafoo bar bazbbb
${=spec}
SH_WORD_SPLIT オプションを一時的にオンにします。 == とするとオフにします。
$ myvar="hello world"
$ for w in $=myvar; do echo $w; done
hello
world
$ for w in $==myvar; do echo $w; done
hello world
${~spec}
GLOB_SUBST オプションを一時的にオンにします。 ~~ とするとオフにします。
$ myvar=/*
$ echo $~myvar
/bin /dev /etc /home /lib /media /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var
$ echo $~~myvar
/*
パラメータ展開フラグ
パラメータ展開の { の直後に ( ) でパラメータ展開フラグを指定できます。
$ myvar="hello world"
$ echo ${(C)myvar}
Hello World
詳細は man ページをご覧ください。
編集子
ヒストリー展開と同様の編集子を利用できます。 : を書いた後に、展開後の文字列に対して編集を行います。
$ myvar=sample.txt
$ echo ${myvar:e}
txt
$ echo ${myvar:r}
sample
$ echo ${myvar:s/txt/log/}
sample.log
e を指定してファイル名の拡張子の部分を取り出したり、 r で拡張子の部分を削除したり、 s を指定して文字列の置換を行ったりしています。
コマンド置換
コマンドを実行し、その標準出力を文字列として置換します。最後の改行は削除されます。
$( )
$( ) で囲んだ文字列はコマンドとして実行され、標準出力を文字列に置換します。
$ whoami
user
$ echo "I am $(whoami)"
I am user
` `
$( ) と同じですが、ネストできないので $( ) の方を使いましょう。
数式展開
数式を計算します。
$(( ))
$(( )) で囲んだ文字列を数式として計算します。
$ echo "1 + 2 + 3 = $(( 1 + 2 + 3 ))"
1 + 2 + 3 = 6
$[ ]
$(( )) と同じですが、非推奨のようですので $(( )) の方を使いましょう。
ブレース展開
文字列の組み合わせや、数列を生成します。
{ , }
文字列中で使用すると、それを展開します。
$ echo aaa{xxx,yyy,zzz}bbb
aaaxxxbbb aaayyybbb aaazzzbbb
ネストすることもできます。
$ echo npm install -D eslint-plugin-{import,prettier,react{,-hooks}}
npm install -D eslint-plugin-import eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks
複数使用すると、すべての組み合わせへと展開されます。
$ echo {a,b,c,d}{w,x,y,z}
aw ax ay az bw bx by bz cw cx cy cz dw dx dy dz
{ .. }
初項と末項を指定して、公差 1 の等差数列を生成します。初項より末項が小さい場合、公差は -1 となります。
$ echo {3..10}
3 4 5 6 7 8 9 10
$ echo {10..0}
10 9 8 7 6 5 4 3 2 1 0
文字を指定すると、その間の文字をすべて列挙します。
$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z
$ echo {ぁ..ゖ}
ぁ あ ぃ い ぅ う ぇ え ぉ お か が き ぎ く ぐ け げ こ ご さ ざ し じ す ず せ ぜ そ ぞ た だ ち ぢ っ つ づ て で と ど な に ぬ ね の は ば ぱ ひ び ぴ ふ ぶ ぷ へ べ ぺ ほ ぼ ぽ ま み む め も ゃ や ゅ ゆ ょ よ ら り る れ ろ ゎ わ ゐ ゑ を ん ゔ ゕ ゖ
{ .. .. }
{ .. } と同じですが、公差を指定できます。
$ echo {0..10..3}
0 3 6 9
文字を指定することはできません。
ファイル名展開
~
~ はホームディレクトリ ($HOME の値) に置き換えられます。
$ echo ~
/home/user
$ echo $HOME
/home/user
~ に続いて数字を書くと、 pushd したディレクトリスタックのパスを参照することができます。 + は +0 として扱われます。負の値を指定すると、ディレクトリスタックの底から数えます。
$ pushd /
$ pushd etc
$ dirs -v
0 /etc
1 /
2 ~
$ echo ~1
/
$ echo ~+
/etc
$ echo ~-0
/home/user
~ に続いて文字列を書くと、名前付きディレクトリを展開します。名前付きディレクトリは通常、システム上のすべてのユーザーのホームディレクトリです。
$ echo ~root
/root
=
= に続いてコマンド名を書くと、そのコマンドのパスに置き換えられます。
$ echo =git
/usr/bin/git
ファイル名生成
いわゆるグロブです。
*
任意の文字列にマッチします。
$ echo /*
/bin /dev /etc /home /lib /media /mnt /opt /proc /root /run /sbin /srv /sys /tmp /usr /var
$ echo /*n
/bin /run /sbin
?
任意の 1 文字にマッチします。
$ echo /????
/home /proc /root /sbin
$ echo /?o??
/home /root
[ ]
[ ] 内のいずれかの 1 文字にマッチします。
$ echo /*[nr]
/bin /run /sbin /usr /var
- で区切ることで文字の範囲を指定できます。
$ echo /*[o-z]
/dev /mnt /opt /root /srv /sys /tmp /usr /var
また、 [ ] の中に更に [: :] を書くことで、 POSIX 文字クラスを指定できます。
$ echo /bin/*[[:digit:]]*
/bin/base64 /bin/linux32 /bin/linux64 /bin/ping6 /bin/zsh-5.8
[^ ], [! ]
[^ ] や [! ] 内に指定した文字以外にマッチします。
$ echo /bin/*[^a-z]*
/bin/base64 /bin/kbd_mode /bin/linux32 /bin/linux64 /bin/ping6 /bin/pipe_progress /bin/run-parts /bin/zsh-5.8
< - >
範囲内の数値にマッチします。値を省略した場合は、無限大の端点となります。つまり <-> はすべての数値にマッチします。
$ echo /bin/*<50-100>*
/bin/base64 /bin/linux64
( )
( ) 内のパターンにマッチします。パターンをグループ化します。
パターンの末尾に書くと、グロブ修飾子となります。詳細は後述。
|
x|y で、 x か y にマッチします。パイプとして扱われるのを避けるため、 ( ) を使用する必要があります。
$ echo /bin/(ch|rm)*
/bin/chgrp /bin/chmod /bin/chown /bin/rm /bin/rmdir
^
要 EXTENDED_GLOB オプション。 ^x で、 x 以外にマッチします。
$ echo /dev/^std*
/dev/console /dev/core /dev/fd /dev/full /dev/mqueue /dev/null /dev/ptmx /dev/pts /dev/random /dev/shm /dev/tty /dev/urandom /dev/zero
^ をエスケープするには \ を前置します。 git コマンドで HEAD^ とか指定するときに面倒臭くなる奴
$ git show HEAD\^
~
要 EXTENDED_GLOB オプション。 x~y で、 x にマッチするものから y を除外します。
$ echo /dev/std*~*in*
/dev/stderr /dev/stdout
#
要 EXTENDED_GLOB オプション。 x# で、 x に 0 回以上マッチします。
**
**/ は (*/)# と等価です。 (空) 、 */ 、 */*/ 、 */*/*/ ... にマッチします。
##
要 EXTENDED_GLOB オプション。 x# で、 x に 1 回以上マッチします。
(# )
要 EXTENDED_GLOB オプション。グロブフラグを指定します。 (# ) の中にフラグを指定すると、 (# ) 以降で作用します。
$ echo /bin/(#i)SLEEP
/bin/sleep
$ echo /bin/(#a3)abcd
/bin/arch /bin/ash /bin/cp /bin/dd /bin/ed /bin/nice /bin/pwd /bin/sed /bin/watch
この例では、 i を指定し、大文字小文字を区別しないようにしたり、 a を指定し曖昧マッチを行なっています。他にもたくさんフラグがあります。詳細は man ページをご覧ください。
( ), (#q )
パターンの末尾にグロブ修飾子を指定します。 (#q ) は要 EXTENDED_GLOB オプション。ファイルの属性でフィルタすることができます。 ** ((*/)#) でマッチしすぎてしまう場合に制約をつけるなどの使用方法があります。
/
ディレクトリ。
$ echo /etc/*(/)
/etc/apk /etc/conf.d /etc/crontabs /etc/init.d /etc/logrotate.d /etc/modprobe.d /etc/modules-load.d /etc/network /etc/opt /etc/periodic /etc/profile.d /etc/ssl /etc/sysctl.d /etc/terminfo /etc/zsh
F
空でないディレクトリ。
.
通常ファイル。
# echo /etc/*a*(.)
/etc/alpine-release /etc/fstab /etc/hostname /etc/inittab /etc/os-release /etc/passwd /etc/shadow
@
シンボリックリンク。
# echo /dev/*(@)
/dev/core /dev/fd /dev/ptmx /dev/stderr /dev/stdin /dev/stdout
=
ソケット。
p
名前付きパイプ。
*
実行可能な通常ファイル。
%
デバイスファイル。
$ echo /dev/*(%)
/dev/console /dev/full /dev/null /dev/random /dev/tty /dev/urandom /dev/zero
%b でブロックスペシャルファイル、 %c でキャラクタスペシャルファイルを指定します。
e, +
グロブ展開結果の 1 件毎にコマンドを実行し、終了コードが 0 であるものだけを残します。
e の場合は (e:'string':) という風にシェルコードを、 + の場合はコマンド名を指定して実行することになります。ファイルパスは $REPLY に格納されています。
$ testshebang() { [[ $(head -1 "$REPLY") =~ ^#! ]] }
$ echo /usr/bin/*(+testshebang)
/usr/bin/ldd
この例ではファイルの先頭に #! があるかどうか判定する関数 testshebang 関数を定義し、それを (+testshebang) というグロブオプションで指定しています。結果、 /usr/bin 内のファイルで #! で始まっているものがグロブされます。
^
^ 以降のグロブ修飾子を反転します。
-
ファイルを指していないシンボリックリンクでも動作するかどうかを切り替えます。例として、 (-@) を指定すると壊れたシンボリックリンクを指定することができます。
:
ヒストリー展開と同様の編集子を利用できます。 : を書いた後に、展開後の文字列に対して編集を行います。
$ echo /*(:u)
/BIN /DEV /ETC /HOME /LIB /MEDIA /MNT /OPT /PROC /ROOT /RUN /SBIN /SRV /SYS /TMP /USR /VAR
その他
パーミッションやソート、オプションの一時切り替えなどたくさんのグロブ修飾子があります。詳細は man ページをご覧ください。
パラメータ (変数)
変数を定義することができます。定義済みの特殊変数もあります。
=
変数を初期化します。
$ myvar=hello
= の前後にスペースを空けてはいけません。 = の前にスペースを空けると myvar =hello となり、 myvar コマンドを実行すると解釈されます。 =hello はコマンドに渡される引数と解釈されます (ファイル名展開が起こります) 。 = の後にスペースを空けると myvar= hello となり、 myvar に空の値をセットした状態で hello コマンドを実行すると解釈されます。
=( )
( ) で囲むと配列を初期化します。
$ myarray=(hello world 1 2 3)
() は空の配列を表します。
$ empty=()
=( [ ]= )
配列の要素のインデックスを指定して初期化できます。
$ myarray=([2]=world [1]=hello)
$ echo $myarray
hello world
[ ]
添字です。文字列に対して添字を使用すると、その位置の文字を返します。配列に対して使用すると、その位置の要素を返します。
$ myvar=hello; myarray=(foo bar baz)
$ echo $myvar[2]
e
$ echo ${myarray[2]}
bar
添字は 0 ではなく 1 が最初の要素となることに注意してください。
添字に * や @ を指定すると、すべての要素を指定したことになります。 $myarray に [*] を指定した場合、 "$myarray[1] $myarray[2] ..." と展開されますが、 [@] を指定した場合、 "$myarray[1]" "$myarray[2]" ... と展開され、異なるクォートとなります。
$ echo $myarray[*]
foo bar baz
[ , ]
添字に範囲を指定します。
$ myvar="assignment"
$ echo $myvar[3,6]
sign
[ ]=
添字で指定した要素に値を代入します。
$ myvar=hello; myarray=(foo bar baz)
$ myvar[2]=a
$ echo $myvar
hallo
$ myarray[2]=hoge
$ echo $myarray
foo hoge baz
[( ) ]
添字フラグを指定します。 ( ) 内にフラグを指定します。
$ myarray=(foo bar baz)
$ echo $myarray[(r)b*]
bar
フラグ r を指定すると、添字にパターンを書くことができます。パターンに最初にマッチした要素が返ります。
他にもたくさんの添字フラグがあります。詳細は man ページをご覧ください。
$0, $1, $2, ...
$0 にはプログラム名が格納されています。 $1 以降は引数の値が格納されています。関数内で参照できます。
$ echo $0
/bin/zsh
$ myfunc() { echo $1 $3 }
$ myfunc foo bar baz
foo baz
$*, $@
すべての引数が格納されています。 $argv と同じです。
$ myfunc() { echo $* }
$ myfunc foo bar baz
foo bar baz
$!
最後に実行開始したバックグラウンドプロセスやコプロセスの PID が格納されています。
$ sleep 3m &
[1] 12345
$ echo $!
12345
$#
引数の数が格納されています。 $ARGC と同じです。
$ myfunc() { echo $# }
$ myfunc foo bar baz
3
$# の直後に文字列を書いた場合、パラメータ展開と解釈されるため、 ${#} と書く必要があります。
$$
シェルの PID が格納されています。
$-
シェル起動時や、 set setopt で指定されたオプションが格納されています。
$ echo $-
569XZims
$?
最後に実行したコマンドの終了コードが格納されています。
$ echo hello
hello
$ echo $?
0
$_
最後に実行したコマンドの最後の引数が格納されています。
$ echo hello world
hello world
$ echo $_
world
他にもたくさんのシェル変数があります。詳細は man ページをご覧ください。
シェル組み込みコマンド
cd など組み込みコマンドは多数ありますが、記号のみの組み込みコマンドもあります。
.
現在のシェルでファイルを読み込んで実行します。
$ cat file.zsh
echo hello
$ . ./file.zsh
hello
:
何もしません。引数は無視されます。
$ : nothing
終了コード 0 を返すので、 while : で無限ループするのによく使われます。
おわり
man ページ zshall(1) には zsh について全部載っています。この記事も man ページを見ながら書きました。この記事に載っていないこともたくさんあるので是非 man ページを読むことをおすすめします。目指せシェル芸人。