あなたの知らない>|と<>の使い方:スマートな紳士のためのシェルスクリプト(6)(2/2 ページ)
>や>>、>&といったひんぱんに使われるリダイレクトに対し、ほとんど使われることのないリダイレクトが>|と<>だ。実際には興味深い機能である、これら「知られざる」リダイレクトについて説明しよう。(編集部)
>と>>の違いはopen(2)時のオプション
次に>>の処理を見てみる。こちらはopen(2)が次のように記述されている。
215 case NAPPEND: 216 fname = redir->nfile.expfname; 217 if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
「O_WRONLY|O_CREAT|O_APPEND, 0666」となっており、書き込み専用で開いていること、ファイルが存在しない場合には0666にマスクを適用したパーミッションで新規作成していることも分かる。
また、その後のオプションが>や>|とは異なっている。O_APPENDが指定されており、追記する設定になっている。>および>|ではここにO_TRUNCが指定されており、ファイルサイズを0に切り詰めて先頭から書き込む処理になっていた。ここの指定の違いがそのまま、>>と>の違いということになる。
トランケートしない上書きリダイレクト、<>
それでは、今回特に紹介したかったリダイレクト、<>の処理を見てみよう。該当部分は次のように記載されている。
185 case NFROMTO: 186 fname = redir->nfile.expfname; 187 if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0)
「O_RDWR|O_CREAT, 0666」で開かれているから、このファイルは読み書きの双方ができる。ファイルが存在しない場合には0666にマスクを適用したパーミッションで新規作成されることになる。トランケートもアペンドも指定されていないから、開いたファイルに先頭から上書きできる。
読み書きの双方ができるリダイレクトといっても使い方が想像しにくいかもしれないが、いくつかの用途で使われることがある。まず、次のようなシェルスクリプトを用意する。
$ cat hello.sh #!/bin/sh echo hello world $ ./hello.sh hello world $
先頭の行に「#!/bin/sh 」のようにシバンが書き込まれているわけだが、時には、<>を使ってこのシバンを書き換える処理を行うことがある(なお、この空白は故意に追加している。後で<>で上書きして書き込みを行う際、あらかじめ、長さが超える分だけ余白を作っておかないと、うまくいかないためだ)。このとき例えば、次のように<>を使うと、先頭のシバンだけを書き換えることができる。
$ echo -n '#!/bin/zsh' 3 hello.sh 1>&3 $ cat hello.sh #!/bin/zsh echo hello world $
ここでは、シェルの仕組みが分からないと実行されているコマンドの意味が分からないかもしれない。
まず、「3<> hello.sh」の指定で、hello.shファイルを読み書き可能な状態でオープンし、アクセスするためのファイルディスクリプタ番号に3を指定している。そして「1>&3」でファイルディスクリプタ1を3と同等にしている。ファイルディスクリプタ1は標準出力だから、つまりechoコマンドの出力先がhello.shファイルに置き換わることになる。
ファイルディスクリプタ0、1、2は、すでに使い方が決まっている。0は標準入力、1は標準出力、2は標準エラー出力だ。>&を使ってファイルディスクリプタ0、1、2をほかのディスクリプタに置き換えれば、コマンドに対する入力や出力を自由に置き換えることができる。
<>はファイルディスクリプタを何も指定しなければ、ファイルディスクリプタ0に割り当てて、ファイルをオープンする。つまり、先ほどのコマンドは次のように書いて動作させることもできる。
$ echo -n '#!/bin/zsh' hello.sh 1>&0 $ cat hello.sh #!/bin/zsh echo hello world $
<>を読み込み側で使用するには、次のようにコマンドを実行すればよい。
$ cat 3 hello.sh 0>&3 #!/bin/zsh echo hello world $
「3<> hello.sh」でhello.shをファイルディスクリプタ3でオープンし、「0>&3」で標準入力をファイルディスクリプタ3へ向けている。<>はデフォルトでファイルディスクリプタ0としてファイルをオープンするため、このコマンドは次のように書くこともできる。
$ cat hello.sh #!/bin/zsh echo hello world $
<>は読み書き双方可能な状態でファイルをオープンするため、入力と出力を同時に実施させることができる。例えば、hello.shの行数をカウントして、行数をコメントとしてスクリプトの最後に追加させるといった処理を、次のように1コマンドで実行できる。
$ awk 'END {print "# lines " NR}' 3 hello.sh 0>&3 1>&3 $ cat hello.sh #!/bin/zsh echo hello world # lines 3 $
「3<> hello.sh」でファイルディスクリプタ3でオープンし、「0>&3 1>&3」で標準入力も標準出力もhello.shへ向けている。「awk 'END {print "# lines " NR}'」はすべてファイルを読み込んだあとで、行数を出力するコマンドだ。このため、上記のように処理が実行される。
記述が複雑になり、理解もしにくくなるため、通常は<>リダイレクトを使うことはない。ただし、どうしても一時ファイルを作成したくないとか、ファイルを直接書き換えたい場合などはこの方法を利用できる。
>や>>しか使っていない状況では、「3<> hello.sh 0>&3 1>&3」といった表記は理解しがたいかもしれないが、シェルのソースコードを読み、内部でどのようなシステムコールに変換されているのかが分かれば、どういった使い方ができるか、簡単に把握できるようになる。
おまけ:open(2)とumask(2)
今回紹介したsh(ash)のソースコードでは、open(2)の段階でパーミッションとして0666を指定している。しかし、実際に作成されるファイルのパーミッションは次のように0644になる。
$ : > hello $ ls -l hello -rw-r--r-- 1 daichi daichi 0 7月 12 12:31 hello $ umask 0022 $
これはumaskの値として0022が設定されているからだ。open(2)はumaskの値を引いた値をパーミッションとして指定する仕組みになっている。0022の値はユーザーがログインする段階で指定されるようになっており、そこから実行されるすべての子プロセスに自動的に引き継がれる。このため、明示的に設定しない限り、0644でファイルが作成されることになる。
Copyright © ITmedia, Inc. All Rights Reserved.