C言語関係掲示板

過去ログ

No765 シェルコマンドをリダイレクトを使わずにファイルから読み込む

[戻る] [ホームページ]
No.83

シェルコマンドをリダイレクトを使わずにファイルから読み込む
投稿者---あかま(2003/06/08 22:07:51)


今、forkやpipeを学んでいて、擬似的なシェルのパイプやリダイレクトを作ってみようと思ったのですが、なかなかこいつらが手強くて…

リダイレクトの実現なのですが、単純に入出力ファイルをopenしてdupでstdin/stdoutに繋いでみたのですが、
ファイルから読み込んでくれなければ、書き込んでもくれない有様です。
どこを改善すればよいでしょうか?

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main(int argc,char *argv[]){
	
	char *command[2];
	int fd,fd2;
	
	command[0] = "sort";
	command[1] = NULL;
	
	switch(fork()){
		case 0:
			if(fd = open("sort_before.txt",O_RDONLY) ==-1){
                printf("open error!1\n");
                exit(-1);
    		    }
			dup2(fd,0);
			
			if(fd2 = open("sort_after.txt",O_WRONLY|O_CREAT,0600) ==-1){
            printf("open error!1\n");
            exit(-1);
    		}
			dup2(fd2,1);
			
			execvp(command[0],command);
			perror("child process1\n");
		case -1:
			perror("child process2\n");
			break;
		
		default:
			wait();
		
	}
}


openしたからにはcloseがいるのかなと思ったりしたのですが、execvpすると子プロセスが終了してしまうので無理のよう。
この場合closeは必要ないのでしょうか?

そしてもう一つ、高水準のfopenでオープンしたファイルをdupでstdin/stdoutに繋げることは出来ますか?
出来るような事が書いてあったのですが、ポインタが違うと警告が出てうまくいきません。

No.87

Re:シェルコマンドをリダイレクトを使わずにファイルから読み込む
投稿者---かずま(2003/06/09 00:51:38)


> ファイルから読み込んでくれなければ、書き込んでもくれない有様です。
> どこを改善すればよいでしょうか?
> if(fd = open("sort_before.txt",O_RDONLY) ==-1){
> if(fd2 = open("sort_after.txt",O_WRONLY|O_CREAT,0600) ==-1){
= より == のほうが優先順位が高いので括弧が必要です。

> 高水準のfopenでオープンしたファイルをdupでstdin/stdoutに繋げることは出来ますか?
  dup2(fileno(fp), 0);


No.88

Re:シェルコマンドをリダイレクトを使わずにファイルから読み込む
投稿者---かずま(2003/06/09 01:48:09)


> どこを改善すればよいでしょうか?

もうひとつ。
   wait(NULL); または wait(&status);


No.123

兄弟プロセスでのpipe
投稿者---あかま(2003/06/10 22:22:48)


アドバイスありがとうございます。
2日悩んで括弧が原因か…

なんとか目的のプログラムも出来てきたのですが、ここでまた問題が…
プログラムというのが、シェルの|,<,>を!,+,=に置き換えて実行できるというもので、例えばシェルで
ls | sort -r > kekka.txt
ならば、
./fake_sh ls ! sort -r = kekka.txt
(fake_shはプログラム名)とすると同様の結果が得られるというものです。
上と同じならば現時点でうまくいくのですが、
./fake_sh ls ! sort -r ! sort
のようにパイプが2つ以上になるとうまく出力されません。
どなたか原因のアドバイスをお願いします。

プログラムでパイプはpfd[10][2]と10個宣言されており、上記のように実行すると
ls →入力デフォルト,出力pfd[0][1]
sort -r →入力pfd[0][0],出力pfd[1][1]
sort →入力pfd[1][0],出力デフォルト
となっています(私の意図したとおりなら)。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main(int argc,char *argv[]){
	
	int i,j,cnt,val,pfd[10][2],check=0;
	char *filename[2];
	char *command[10];
	int fd,fd2;
	
	j = 0;
	i = 1;
	while(1){
		for(cnt = 0;i < argc && *argv[i] != '!' && *argv[i] != '+' && *argv[i] != '='; i++,cnt++){
		 	command[cnt] = argv[i];
		}
		if(cnt ==0){
		     break;
		}
		command[cnt] = NULL;
		
		if(i <argc && *argv[i] == '+'){
			i++;
			filename[0] = argv[i];
			i++;
			check |=1;
		}
		if(i <argc){
			switch(*argv[i]){
				case '=':
					i++;
					filename[1] = argv[i++];
					check |= 2;
					break;
				case '!':
					i++;
					pipe(pfd[j]);
					check |=4;
					break;
			}
		}
		switch(fork()){
			case 0:
				if(check & 8){//1つ前の子プロセスからパイプで渡された時
					dup2(pfd[j-1][0],0);
					close(pfd[j-1][1]);
				}
				if(check & 1){//リダイレクトでファイルから入力
					if((fd = open(filename[0],O_RDONLY)) ==-1){
	                printf("open error!\n");
	                exit(-1);
	    		    }
					dup2(fd,0);
				}
				if(check & 2){//リダイレクトでファイルへ出力
					if((fd2 = open(filename[1],O_WRONLY|O_CREAT,0600)) ==-1){
	                printf("open error!\n");
	                exit(-1);
	    		    }
					dup2(fd2,1);
				}
				
				if(check & 4){//パイプへ出力
					dup2(pfd[j][1],1);
					close(pfd[j][0]);
				}
				execvp(command[0],command);
				perror("child process1\n");
			case -1:
				perror("child process2\n");
				break;
			
			default:
				if(check & 4){
					check = 8;
					j++;
				}
				else{
					check = 0;
				}
		}
	}
	wait(&val);
}


ちなみにパイプやリダイレクトは、半角スペースで一つ一つ区切らないとうまくいかないです。
タイトル変えたときはスレッド新しく立てた方がいいじゃろか?

No.132

Re:兄弟プロセスでのpipe
投稿者---物見遊山(2003/06/12 17:23:43)


昼に出した亀レスはやっぱり動かなかったので修正してみました。
多分動くと思いますが、まだ何かが潜んでいるかもしれません。
#親プロセスでパイプを開いているのに
#親プロセスのパイプを閉じていないことが原因みたいです。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

main(int argc, char *argv[])
{
    int i = 1, cnt, pfd[2], check = 0;
    int newin, newout;
    char *filename[2];
    char *command[10];
    int fd, fd2;

    while (1) {
        for (cnt = 0;
            i < argc && *argv[i] != '!' && *argv[i] != '+'
            && *argv[i] != '='; i++, cnt++) {
            command[cnt] = argv[i];
        }
        if (cnt == 0) {
            break;
        }
        command[cnt] = NULL;

        if (i < argc && *argv[i] == '+') {
            i++;
            filename[0] = argv[i];
            i++;
            check |= 1;
        }
        if (i < argc) {
            switch (*argv[i]) {
            case '=':
                i++;
                filename[1] = argv[i++];
                check |= 2;
                break;
            case '!':
                i++;
                pipe(pfd);
                newout = pfd[1];
                check |= 4;
                break;
            }
        }
        switch (fork()) {
        case 0:
            if (check & 8) {    //1つ前の子プロセスからパイプで渡された時
                dup2(newin, 0);
                close(newin);
            }
            if (check & 1) {    //リダイレクトでファイルから入力
                if ((fd = open(filename[0], O_RDONLY)) == -1) {
                    printf("open error!\n");
                    exit(-1);
                }
                dup2(fd, 0);
            }
            if (check & 2) {    //リダイレクトでファイルへ出力
                if ((fd2 =
                    open(filename[1], O_WRONLY | O_CREAT,
                        0600)) == -1) {
                    printf("open error!\n");
                    exit(-1);
                }
                dup2(fd2, 1);
            }

            if (check & 4) {    //パイプへ出力
                dup2(newout, 1);
                close(newout);
            }
            execvp(command[0], command);
            perror("child process1\n");
        case -1:
            perror("child process2\n");
            break;

        default:
            if (check & 4) {
                close(pfd[1]);
                newin = pfd[0];
                check = 8;
            } else {
                close(pfd[0]);
                check = 0;
            }
        }
    }
    return 0;
}



No.133

Re:兄弟プロセスでのpipe
投稿者---あかま(2003/06/13 15:06:01)


動いた(涙)
forkもdupもpipeも初めて使ったので途方にくれておりました。
なかなかこの辺の使い方を詳しく説明しているサイトってないんですよね。
ありがとうございました。

>#親プロセスでパイプを開いているのに
>#親プロセスのパイプを閉じていないことが原因みたいです。
実はいらないpipeはcloseしないと動かないことに気付くのにかなり時間がかかったりしてました。
使ってなくても宣言した親プロセスでクローズがいるとは。よく憶えておきます。

最後に一つ、
dup2(newin, 0);
close(newin);

これなのですが、dupした後にcloseして、一見するとそのpipeが使えなくなっているように見えます。
当然、pipeが繋がっている標準入力も…
というように思うのですが、closeしてあってもきっちり動いているので、
このcloseはなにを閉めているのでしょう?

No.134

Re:兄弟プロセスでのpipe
投稿者---物見遊山(2003/06/13 18:59:49)


>動いた(涙)

おめでとー(拍手)。
実はものみも何でこれで動くのかよくわかってないのですが(汗)。

>最後に一つ、
>
> dup2(newin, 0);
> close(newin);
>
>
> これなのですが、dupした後にcloseして、一見するとそのpipeが使えなくなっているように見えます。
> 当然、pipeが繋がっている標準入力も…
> というように思うのですが、closeしてあってもきっちり動いているので、
> このcloseはなにを閉めているのでしょう?

dup2(2)とclose(2)の順番が逆なら仰るとおり
pipeの入り側が閉じられたことになります(そしてdup2(2)がエラーを返す)。

上記の場合、dup2(2)した時点ではpipeはY字型になってるので
Iの字にするためclose(2)してます(かえって混乱する説明?)。

ファイル記述子ってポインタの配列の添え字なんですよ。
dup2(2)する前ってその配列の内容って・・・

filedesc[0] : stdoutを管理するデータへのアドレスを格納
filedesc[newin] : pipe(入り側)を管理するデータへのアドレスを格納

・・・ってなってて、dup2(2)を発行するとまず・・・

filedesc[0] : dup2(2)すれば、舞台裏でcloseされるのでNULL
filedesc[newin] : pipe(入り側)のアドレスを格納

・・・ってなった後、

filedesc[0] : pipe(入り側)を管理するデータへののアドレスを格納(dup2(2)がコピー)
filedesc[newin] : pipe(入り側)を管理するデータへののアドレスを格納

となります。

で、filedesc[0]とfiledesc[newin]って同じ場所指してるじゃないですか。
#これって何か不味そうですよね?
で、close(2)します。close(2)すれば・・・

filedesc[0] : pipe(入り側)を管理するデータへののアドレスを格納
filedesc[newin] : close(2)されたのでNULL

・・・つーわけでpipeは生き残ります。

No.135

Re:兄弟プロセスでのpipe
投稿者---あかま(2003/06/13 20:42:49)


dupすると、入り口(or出口)が増えていくんですね。
よくわかりました。色々ありがとうございました。

さぁーて、後は使えるようになったpipeを活用する場だが…
あんまり使う場面てないんだよなぁ(笑)