>や>>、>&といったひんぱんに使われるリダイレクトに対し、ほとんど使われることのないリダイレクトが>|と<>だ。実際には興味深い機能である、これら「知られざる」リダイレクトについて説明しよう。(編集部)
シェルが提供する機能はカーネルが提供している機能をダイレクトに利用するものが多い。つまり、シェルの記述がダイレクトにシステムコールに結び付くような機能が多いということだ。コマンドの実行、パイプ、リダイレクトなどは、そっくりそのままシステムコールに置き換わる。
リダイレクトであれば、ほとんどのケースで>ないしは>>で事足りるはずだ。2>&1という記述はこれで1つの機能に思えるが、これは>&というリダイレクトの典型的な使い方の1つであり、つまりはリダイレクトだ。
>、>>、>&はよく使われるリダイレクトといえる。しかし、sh(1)のマニュアルには次のように、9つのリダイレクトが紹介されている。
Redirection operators: < > << >> <> <& >& <<- >|
この中でも特になじみのないリダイレクトが>|と≪≫だろう。実際、これらのリダイレクトが使われることはほとんどない。
しかし実はこれらは興味深い機能であり、シェルを極めていくのであれば、ぜひ使い方を知っておきたい。今回はsh(1)のソースコードを読みながら、これらリダイレクトが何をするものかを紹介する。
シェルのソースコードを読むとなると、FreeBSD sh(ash)が適度なソースコード量で扱いやすい。リダイレクトの処理は/usr/src/bin/sh/redir.cに記載されている。
>のリダイレクトを処理しているのはredir.cの190行目のcase NTO:からの行だ。-Cオプションを処理するため、また>|と処理コードを共有するために若干読みにくくなっているが、最終的にはopen(2)システムコールに置き換わっていることが分かる。
190 case NTO: 191 if (Cflag) { 192 fname = redir->nfile.expfname; 193 if (stat(fname, &sb) == -1) { 194 if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) 195 error("cannot create %s: %s", fname, strerror(errno)); 196 } else if (!S_ISREG(sb.st_mode)) { 197 if ((f = open(fname, O_WRONLY, 0666)) < 0) 198 error("cannot create %s: %s", fname, strerror(errno)); 199 if (fstat(f, &sb) != -1 && S_ISREG(sb.st_mode)) { 200 close(f); 201 error("cannot create %s: %s", fname, 202 strerror(EEXIST)); 203 } 204 } else 205 error("cannot create %s: %s", fname, 206 strerror(EEXIST)); 207 goto movefd; 208 } 209 /* FALLTHROUGH */ 210 case NCLOBBER: 211 fname = redir->nfile.expfname; 212 if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) 213 error("cannot create %s: %s", fname, strerror(errno)); 214 goto movefd;
caseで指定されている値は、パーサを通して分解されたパーツを示している。/usr/src/bin/sh/nodetypesにまとまった説明があるので見ておきたい。NTO:は>、NCLOBBERは>|だ。>>にはNAPPENDが割り当てられており、<>はNFROMTOが割り当てられている。
115 NTO nfile # fd> fname 116 NFROM nfile # fd< fname 117 NFROMTO nfile # fd<> fname 118 NAPPEND nfile # fd>> fname 119 NCLOBBER nfile # fd>| fname
NTO:の処理は、-Cが指定されている場合に、次のようにファイルをオープンする処理になっている。-Cは、「既存のファイルが存在する場合には>での上書きは実施しない」というオプションだ。
190 case NTO: 191 if (Cflag) { ... 194 if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0)
次のように-Cを指定してsh(1)を実行すると動きが分かる。-Cが指定されている場合、>でファイルを新規作成することはできるが、ファイルが存在する場合には上書きできない。>|は、たとえ-Cが指定されている場合でも、既存のファイルへの上書きを実施するためのリダイレクトだ。
% sh -C $ echo test > file $ echo test > file cannot create file: ファイルが存在します $ echo test >| file $
つまり、-Cが指定されている場合には処理が異なるが、-Cが指定されていない場合には>と>|は同じ処理をすることになる。case NTO:に入ると、-Cが指定されていない場合にはそのままcase NCLOBBER:の処理に移っているのはこのためだ。>と>|で処理を共有している。
210 case NCLOBBER: 211 fname = redir->nfile.expfname; 212 if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) 213 error("cannot create %s: %s", fname, strerror(errno)); 214 goto movefd;
>はつまり、「O_WRONLY|O_CREAT|O_TRUNC, 0666」のオプションでファイルを開くという処理をしていることになる。ファイルは書き込み専用で開き、ファイルが存在しない場合には0666にマスクを適用したパーミッションで新規作成し、開くと同時にトランケートを実施し、ファイルサイズを0にしてから書き込みを開始する、ということになる。
ファイルを開いたあとは、movefd:へ処理を移している。
175 movefd: 176 if (f != fd) { 177 if (dup2(f, fd) == -1) { 178 e = errno; 179 close(f); 180 error("%d: %s", fd, strerror(e)); 181 } 182 close(f); 183 } 184 break;
開いたファイルが出力先、または読み込み元になるようにdup2(2)でコピーしていることが分かる。
177 if (dup2(f, fd) == -1) { ... 179 close(f); ... 181 } 182 close(f);
このように、実際にシェルのソースコードを読むことで、実際にはどのような動きをするものかがよく分かる。
Copyright © ITmedia, Inc. All Rights Reserved.