zshのprint関数

zshの組込み関数 (というのか?) は、man しても説明が得られない。

print という zsh 特有の関数については、Google しても日本語圏からはほとんど有用な情報が得られなかった。(それでいてみんなけっこう .zshrc とかに使ってたりするんだよなあ…どこで知ったんだろう)

というわけでドキュメントを見た。

以下は訳というか自分なりの解釈みたいなもん。

print [ -abcDilmnNoOpPrsz ] [ -u n ] [ -f format ] [ -C cols ] [ -R [ -en ] ] [ arg ... ]

フラグを立てないか、"-" フラグだけのときは、ほぼ echo コマンドと同じ。ただし、以下のエスケープ文字列が使える。

\M-x \Mx	(Meta+x)
\C-x \Cx	(Ctrl+x)
\E      	(=\e)

例 (これ以降、行頭に zsh% とあるものはプロンプトを表す)

zsh% print "abc\C-hdef"
abdef	(\C-h=backspace で c が消えた) 
-b

bindkeyコマンドに使えるエスケープが使える。
(フラグ無しの場合との違いがわからない)

\a bell character
\b backspace
\e, \E escape
\f form feed
\n linefeed (newline)
\r carriage return
\t horizontal tab
\v vertical tab
\NNN character code in octal
\xNN character code in hexadecimal
\M[-]X character with meta bit set
\C[-]X control character
^X control character
http://zsh.dotsrc.org/Doc/Release/zsh_17.html
-c, -C, -a

-c を付けると列表示 (columns の c)。-C n (n は整数) だと列の数を指定。-a を付けると、列方向ではなく行方向に並べる。

zsh% print `ls`
0th_file 1st_file 2nd_file 3rd_file 4th_file 5th_file 6th_file 7th_file 8th_file 9th_file
zsh% print -c `ls`
0th_file  2nd_file  4th_file  6th_file  8th_file
1st_file  3rd_file  5th_file  7th_file  9th_file
zsh% print -ca `ls`
0th_file  1st_file  2nd_file  3rd_file  4th_file  5th_file  6th_file  7th_file  
8th_file  9th_file  
zsh% print -C 3 `ls`
0th_file  4th_file  8th_file
1st_file  5th_file  9th_file
2nd_file  6th_file
3rd_file  7th_file
zsh% print -C 3 -a `ls`
0th_file  1st_file  2nd_file  
3rd_file  4th_file  5th_file  
6th_file  7th_file  8th_file  
9th_file  

(↑見にくいな…)

-D

ディレクトリ名として出力。ホームディレクトリが ~ に置き換わる。(directory の d)

zsh% print $PWD
/Users/atsushi
zsh% print -D $PWD
~
-l

スペースではなく改行で分ける。

zsh% print -l a "b c" d
a
b c
d
-m

フラグではない最初の引数をワイルドカードとして、他の引数の中からマッチするものだけ出力。

zsh% print -m "a" a b ab abc 
a
zsh% print -m "*a" a b ab abc
a
zsh% print -m "a?" a b ab abc
ab
-n, -N

-n は echo と同じで、最後に改行をつけない。(no new line の n)
-N は、それぞれの出力の間に文字を挟まない。

zsh% print -n "a b" c    
a b c% (←行末の"%"は次の行の意)
zsh% print -N "a b" c
a bc%
-o, -O, -i

-o だと辞書の昇順に出力。(order の o)
-O だと辞書の降順に出力。
-i を付けると、大文字・小文字を区別しない。(case-independent の i)

zsh% print -o a b aB B Ab AB ab A
A AB Ab B a aB ab b
zsh% print -oi a b aB B Ab AB ab A
a A aB Ab AB ab B b
zsh% print -O a b aB B Ab AB ab A 
b ab aB a B Ab AB A
zsh% print -Oi a b aB B Ab AB ab A
b B ab aB Ab AB A a
-p

後述。

-P

プロンプト表示用のエスケープ文字が使える。以下参照。(prompt の P)

-r

echo のエスケープを無視。(ignore の r?)

zsh% print "\t"   
	 (←タブ文字)
zsh% print -r "\t"
\t
-R

BSD echo をエミュレートする。-e を付けない限りエスケープしない。-R の後には、 -e と -n のみが付けられる。(-r からの類推)

zsh% print -R "\x21"   
\x21
zsh% print -R -e "\x21"
!
-s

標準出力ではなく、history のリストに出力。(history の s?)

zsh% print -s `date`
zsh% history -1
 3375  Mon 29 Sep 2008 14:37:10 EDT
-u n

"n" というファイル descriptor に出力。

-z

出力を編集バッファに入れる。

zsh% print -z $PWD  
zsh% /temp	(←などと入力された状態で次の行が出る)

zsh の coprocess

-p

-p フラグは、coprocess に出力するということらしいが、よく意味が分からなかったのでさらに検索してみた。

下の説明がすばらしかったので、以下はそれの訳。

coprocess とは

coprocess (以下coproc) は次のようにスタートする。

coproc hogehoge

hogehoge は、標準入力から読んで標準出力に書き出すようなコマンドであることが多く、また、そうでないと coproc にする意味がない。

coproc との通信には、以下の4パターンがある。

print -p	(coproc に入力)
read -p	(coproc からの出力を読む)
fugafuga >&p	(fugafuga コマンドの出力を coproc に渡す)
fugafuga <&p	(coproc からの出力を fugafuga コマンドに渡す)

端的な例を以下に示す。(zsh% はプロンプトを表す)

zsh% coproc while read line; do print -r -- "$line:u"; done

&を付ける必要はない。何故なら、coproc は元からバックグラウンドで処理されるからである。

上記の coproc が動いている間は、このように coproc と通信することができる。

zsh% print -p foo ; read -ep
FOO

(read -e は、読み込んだものをすぐに echo するオプション)

上の例の替わりに、

zsh% coproc tr a-z A-Z
zsh% print -p foo; read -ep

とすると、思うような結果は得られない。何故なら tr コマンドは、バッファー (1024バイトであることが多い) が一杯になるか、end of file (EOF) を受け取るまでは処理を行わないからである。つまり、tr がさらなる入力を待つ間、read -ep は待ち続けることになる。

注意すべきところは、zsh の coproc は、別の coproc が起動されるまで EOF を受け取らないのである。他のシェルで fugafuga >&p と同等のことをやると、fugafuga コマンドが終了するときに coproc の descriptor を捨てるので、coproc は EOF を受け取るのだが 、zsh では、coproc の descriptor を開き続けるので、何度でも同じ coproc に出力することが出来る。しかし、coproc の descriptor は1つまでしか無いことになっているので、別の coproc を起動したときに最初の coproc の descriptor が閉じられることになる。

coproc の descriptor を閉じる簡単な方法は、

zsh% coproc exit

と入力することである。この場合でも、最初の例でいうと while ループは終了しないので、自分で kill する必要がある。もし "setopt no_hup" を常に使うなら、これは重要なステップである。

入力 buffer について一言。最初の例では、 "print -p foo;" で、"print" をフォアグラウンドに残した。これは、この coproc が何らかの出力を行うまでに1行の入力を受け ("while read line" ループ) 、すなわち、print は正常に終了するということが分かっていたからである。もし、数バイトごとに読んでから処理をする別の coproc だったとしたら、"read -ep" は print に阻まれてしまって永久に実行されないかもしれない。だから、普通は coproc に出力するときはバックグラウンドから行うのが普通である。

zsh% cat /etc/termcap >&p &

最後に、これはバグと呼んでも良いかもしれないのだが、

coproc tail
coproc head >&p

のように coproc をパイプできるように思ってしまうが、これは、

「tail を coproc で走らせてから、新しい coroc の head を起動して、その出力を tail に流す」

とはならない。zsh は、新たな redirection を造る前に、既に前の coproc の descriptor を捨ててしまっているので、"coproc head >&p" というのは、

「出力を自分の入力に繋げて head を起動する」

になってしまい、CPU の負荷がエライことになってしまう。

実際

coproc はどういうことに使えるのだろう? まだよくわからん。