連載
» 2012年07月13日 00時00分 UPDATE

スマートな紳士のためのシェルスクリプト(6):あなたの知らない>|と<>の使い方 (1/2)

>や>>、>&といったひんぱんに使われるリダイレクトに対し、ほとんど使われることのないリダイレクトが>|と<>だ。実際には興味深い機能である、これら「知られざる」リダイレクトについて説明しよう。(編集部)

[後藤大地,BSDコンサルティング株式会社]

あなたの知らないリダイレクト、>|と<>

 シェルが提供する機能はカーネルが提供している機能をダイレクトに利用するものが多い。つまり、シェルの記述がダイレクトにシステムコールに結び付くような機能が多いということだ。コマンドの実行、パイプ、リダイレクトなどは、そっくりそのままシステムコールに置き換わる。

 リダイレクトであれば、ほとんどのケースで>ないしは>>で事足りるはずだ。2>&1という記述はこれで1つの機能に思えるが、これは>&というリダイレクトの典型的な使い方の1つであり、つまりはリダイレクトだ。

 >>>>&はよく使われるリダイレクトといえる。しかし、sh(1)のマニュアルには次のように、9つのリダイレクトが紹介されている。

Redirection operators:
         <     >     <<    >>    <>
         <&    >&    <<-   >|

 この中でも特になじみのないリダイレクトが>|と≪≫だろう。実際、これらのリダイレクトが使われることはほとんどない。

 しかし実はこれらは興味深い機能であり、シェルを極めていくのであれば、ぜひ使い方を知っておきたい。今回はsh(1)のソースコードを読みながら、これらリダイレクトが何をするものかを紹介する。

密接に関係する>と>|

 シェルのソースコードを読むとなると、FreeBSD shash)が適度なソースコード量で扱いやすい。リダイレクトの処理は/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);

 このように、実際にシェルのソースコードを読むことで、実際にはどのような動きをするものかがよく分かる。

       1|2 次のページへ

Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。