あなたの知らない>|と<>の使い方:スマートな紳士のためのシェルスクリプト(6)(1/2 ページ)
>や>>、>&といったひんぱんに使われるリダイレクトに対し、ほとんど使われることのないリダイレクトが>|と<>だ。実際には興味深い機能である、これら「知られざる」リダイレクトについて説明しよう。(編集部)
あなたの知らないリダイレクト、>|と<>
シェルが提供する機能はカーネルが提供している機能をダイレクトに利用するものが多い。つまり、シェルの記述がダイレクトにシステムコールに結び付くような機能が多いということだ。コマンドの実行、パイプ、リダイレクトなどは、そっくりそのままシステムコールに置き換わる。
リダイレクトであれば、ほとんどのケースで>ないしは>>で事足りるはずだ。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.