HOWTO · C
C 言語の dup2 関数
この記事では、C 言語で dup2 関数を使用する方法を説明します。
このページの内容
POSIX C の dup2(oldfd, newfd) は、newfd が oldfd と同じオープンファイル記述を参照するようにします。ファイルの読み書きの前に標準入力や標準出力をリダイレクトする用途でよく使われます。呼び出しに失敗すると -1 を返して errno を設定するため、戻り値を確認してください。
dup2 の動作と使いどころ
宣言は int dup2(int oldfd, int newfd); で、<unistd.h> から利用できます。成功すると newfd を返します。newfd がすでに開いている場合は、そのファイルディスクリプタを閉じてから再利用します。この置き換えはクローズと複製の操作に関してアトミックなので、複数のスレッドと並行して動く可能性があるコードでのリダイレクトに役立ちます。
2つのファイルディスクリプタは同じオープンファイル記述を参照します。そのため、ファイルオフセットとファイル状態フラグを共有します。どちらのファイルディスクリプタで読み書きしても、同じオフセットが進みます。一方、ファイルディスクリプタフラグは各ファイルディスクリプタに属します。dup2 が作成する新しいファイルディスクリプタは、oldfd の FD_CLOEXEC を引き継ぎません。
oldfd と newfd が同じ場合、dup2 はそのファイルディスクリプタを閉じずに返します。この場合、そのファイルディスクリプタはすでに有効でなければなりません。ファイルディスクリプタ番号はプロセスごとに異なり、開いているファイルディスクリプタによって変わります。ファイルを開くと必ず番号3になる、あるいはどのプロセスにも常に3つのファイルディスクリプタがある、と決めつけないでください。
dup2 で標準出力をリダイレクトする
書き込み先を開き、そのファイルディスクリプタを STDOUT_FILENO に複製します。この短いプログラムは出力を一時ファイルにリダイレクトし、元の標準出力を復元してから、保存した行を表示します。POSIX の一時ファイル関数とファイルディスクリプタ関数を使用します。
次の例を実行してください。
このセクションのコードと表示されるラベルや出力は英語のままです。
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char path[] = "/tmp/dup2-demo-XXXXXX";
int file = mkstemp(path);
if (file == -1) return 1;
int saved_stdout = dup(STDOUT_FILENO);
if (saved_stdout == -1) {
close(file);
unlink(path);
return 1;
}
if (dup2(file, STDOUT_FILENO) == -1) {
close(saved_stdout);
close(file);
unlink(path);
return 1;
}
int write_ok = puts("Hello through dup2") != EOF && fflush(stdout) == 0;
int restore_ok = dup2(saved_stdout, STDOUT_FILENO) != -1;
close(saved_stdout);
if (!write_ok || !restore_ok) {
close(file);
unlink(path);
return 1;
}
if (lseek(file, 0, SEEK_SET) == (off_t)-1) {
close(file);
unlink(path);
return 1;
}
char buffer[64];
ssize_t length = read(file, buffer, sizeof(buffer) - 1);
close(file);
unlink(path);
if (length < 0) return 1;
buffer[length] = '\0';
if (printf("Captured: %s", buffer) < 0) return 1;
return 0;
}
Captured: Hello through dup2
提示された実行では Captured: Hello through dup2 が記録されました。プログラムは最初の行を一時ファイルに書き込み、標準出力を復元してから保存したバイト列を読み取り、その行を表示します。本番プログラムでは、データに適した保存先のパスとアクセス権を選んでください。この例の mkstemp は、プライベートな一時ファイルを作成します。
dup2 で標準入力をリダイレクトする
標準入力経由でファイルを読み込ませるには、ファイルを読み取り専用で開き、そのファイルディスクリプタを STDIN_FILENO に複製します。呼び出しが成功すると、stdin から読み取る fgets などの関数は、そのファイルから読み込みます。標準入力が同じオープンファイル記述を参照するようになるため、複製後は open が返したファイルディスクリプタを閉じられます。
次の完全な例では、一時入力ファイルを作成して標準入力をそこへリダイレクトし、fgets で1行を読み取ります。読み取った内容がわかるように、その行を表示します。一時ファイルはこの例の入力として使います。既存のファイルにこの方法を適用する場合は、ファイルの内容や fgets の処理を置き換えてください。
このセクションのコードと表示されるラベルや出力は英語のままです。
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
char path[] = "/tmp/dup2-input-XXXXXX";
int input = mkstemp(path);
if (input == -1) return 1;
const char line[] = "Read through redirected stdin\n";
ssize_t written = write(input, line, sizeof line - 1);
if (written != (ssize_t)(sizeof line - 1) || lseek(input, 0, SEEK_SET) == (off_t)-1) {
close(input);
unlink(path);
return 1;
}
if (dup2(input, STDIN_FILENO) == -1) {
close(input);
unlink(path);
return 1;
}
close(input);
unlink(path);
char buffer[80];
if (fgets(buffer, sizeof buffer, stdin) == NULL) return 1;
if (printf("Read: %s", buffer) < 0) return 1;
return 0;
}
実際の入力パスを使う open の形式は int input = open("input.txt", O_RDONLY); です。戻り値が -1 でないことを確認し、dup2(input, STDIN_FILENO) を呼び出して、成功したら input を閉じます。その後 stdin から読み取るデータは、そのファイルから取得されます。完全な例では、同じ手順で一時ファイルに書き込んだ行を読み取ります。ファイルを開く操作、複製、または読み取りに失敗した場合はエラーを処理し、コードがまだ所有しているファイルディスクリプタをすべて閉じてください。
dup と dup2 の違い
どちらの関数も、同じオープンファイル記述を参照する別のファイルディスクリプタを作成します。dup(oldfd) は、現在使用可能な番号のうち最小のものを選んで返します。dup2(oldfd, newfd) は指定した番号を対象とし、すでに開いている対象があれば先に閉じます。使用可能な番号ならどれでもよい場合は dup を、標準入力や標準出力のように特定のファイルディスクリプタを元のファイルに対応させたい場合は dup2 を使います。
たとえば int copy = dup(file); は、新しいファイルディスクリプタを返すか、失敗時に -1 を返します。copy を使う前に戻り値を確認し、どちらも不要になったら両方のファイルディスクリプタを閉じてください。返される番号が3になる保証はありません。番号はプロセスで開いているファイルディスクリプタによって変わります。
エラーとファイルディスクリプタの寿命を扱う
返されたファイルディスクリプタを使う前に、すべての open、dup、dup2 の結果を確認してください。失敗すると -1 を返し、errno を設定します。perror("dup2") を使うと、現在のエラーを報告できます。失敗した戻り値をファイルディスクリプタとして使ってはいけません。複数の手順からなるリダイレクトの途中で失敗した場合は、コードが開いたファイルディスクリプタや保存しておいたファイルディスクリプタを閉じてください。プログラムが元のストリームで処理を続ける必要があり、標準入力や標準出力をすでに変更している場合は、それも復元します。
リダイレクトに成功した後は、元の入力元ファイルディスクリプタを閉じてもリダイレクトは解除されません。標準のファイルディスクリプタは開いたままで、共有されたオープンファイル記述を参照します。保存したファイルディスクリプタや複製した別のファイルディスクリプタは、不要になった時点で閉じてください。STDOUT_FILENO を変更または復元する前に、バッファリングされた出力をフラッシュします。C の標準入出力バッファは、カーネルのファイルディスクリプタ操作とは別に管理されます。こうした確認により、ファイルディスクリプタ番号を決めつけずに、置き換えと後片付けを確実に行えます。