UNIXプログラミング入門~ファイル編(2/2)~
c/c++でunix系osプログラミングを行うときに使うunistd.h
やその周辺のライブラリのお話その2です。
このメモでは、ファイルの入出力に関して取り上げます。
前回は、ファイルのopen
とclose
だけを取り扱ったので、まったく実用的ではありませんでしたが、今回は、open
、close
に加えてread
、write
を取り扱うので多少は実用ファイル操作ができるようになります。
データの読み取り-read-
データの読み取りには、read
関数を利用します。
read
関数のプロトタイプはunistd.h
で以下のように宣言されてます。
int read(int fd, void* buf, int count);
第一引数fd
で指定されたファイルから最大で第三引数count
バイトのデータを読み込んで、第二引数buf
に格納します。
戻り値は、実際に読み込んだバイト数です。
必ずしもcount
バイト読み込むわけではありません。
特にネットワークソケットやFIFO等のような、読み書きに待ちが生じるようなファイルディスクリプタを指定した場合には、ファイルの終端に達しなくともcount
よりも小さな値が返ることがあります。
ファイルの終端に達したときは0
が返ります。
利用例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 128
int main(int argc, char const *argv[])
{
char buf[BUF_SIZE];
int rsize = 0;
int fd;
for(size_t i = 1; i < argc; i++)
{
if ((fd = open(argv[i], O_RDONLY)) < 0)
{
fprintf(stderr, "ファイルを開けませんでした\n");
return -1;
}
while (rsize = read(fd, buf, BUF_SIZE - 1))
{
if (rsize < 0)
{
fprintf(stderr, "読み込み中に問題が起きました\n");
close(fd);
return -1;
}
buf[rsize + 1] = 0;
printf("%s", buf);
}
close(fd);
}
return 0;
}
上記の例だとcat
コマンドと似たようなことを行っています。
データの書き込み-write-
データの書き込みにはwrite
関数を利用します。
write
関数のプロトタイプはunistd.h
で以下のように宣言されてます。
int write(int fd, void* buf, int count);
第一引数fd
で指定されたファイルに第二引数buf
に含まれるデータを最大で第三引数count
バイト書き込みます。
戻り値は、実際に書き込んだバイト数です。
必ずしもcount
バイト書き込むわけではありません。read
同様、特にネットワークソケットやFIFO等のような、読み書きに待ちが生じるようなファイルディスクリプタを指定した場合には、バッファーの終端に達しなくともcount
よりも小さな値が返ることがあります。
利用例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 128
int main(int argc, char const *argv[])
{
char buf[BUF_SIZE];
int rsize = 0;
int wsize = 0;
int rfd, wfd;
for (size_t i = 2; i < argc; i+=2)
{
if ((rfd = open(argv[i-1], O_RDONLY)) < 0)
{
fprintf(stderr, "ファイルを開けませんでした\n");
return -1;
}
if ((wfd = open(argv[i], O_WRONLY | O_CREAT)) < 0)
{
fprintf(stderr, "ファイルを開けませんでした\n");
return -1;
}
while (rsize = read(rfd, buf, BUF_SIZE))
{
if (rsize < 0)
{
fprintf(stderr, "読み込み中に問題が起きました\n");
close(rfd);
close(wfd);
return -1;
}
for (int j = 0; j < rsize; ++j)
{
wsize = write(wfd, buf + j, rsize - j);
if (wsize < 0)
{
fprintf(stderr, "書き込み中に問題が発生しました\n");
close(rfd);
close(wfd);
}
j += wsize;
}
}
close(rfd);
close(wfd);
}
return 0;
}
上記の例ではファイルのコピーを行っています。
標準入出力のファイルディスクリプタ
unistd.h
では標準入出力に対応するファイルディスクリプタは以下のように定義されています。
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
これらのファイルディスクリプタは、open
関数で開かなくとも利用できます。
エラーの種類を確認する
unistd.h
で定義されている関数の多くは、エラーが起きたときに-1
を返しますが、それだけではどんな理由でエラーが起きたかわかりません。
エラーの種類はグローバル変数errno
に記録されているのでエラーが発生したらこれを確認しましょう。(要#include <errno.h>
)
strerror_r
関数を利用するとエラー番号からそれに対応する文字列を取り出すことができます。(要#include <string.h>
)
例えば、明らかに無効なファイルディスクリプタからデータを読み込む例を考えてみます。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h> // グローバル変数 errno
#include <string.h> // strerror_r関数
#define BUF_SIZE 128
int main(int argc, char const *argv[])
{
char errmsg[BUF_SIZE];
char buf[BUF_SIZE];
int rsize = 0;
int fd = 0x1234;// 無効なファイルディスクリプタ
while (rsize = read(fd, buf, BUF_SIZE))
{
if (rsize < 0)
{
strerror_r(errno, errmsg, sizeof(errmsg));
printf("ERROR : %s\n", errmsg);
return -1;
}
}
close(fd);
return 0;
}
出力
ERROR : Bad file descriptor
このようにエラーの種類を取り出すことができます。
まとめ
- データの読み込みには
read
関数、書き込みにはwrite
関数を使う - どちらも、必ずしも第三引数で指定したサイズ分のデータを読み書きするわけではないので実際の読み書きしたサイズは戻り値で確認すること