質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.58%

Rustで子プロセスとの入力/出力を行いたい

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 604

ta1g3n

score 35

前提・実現したいこと

Rustで以下のプログラム(以下①と記します)を子プロセスとして起動し、単純な入出力の中継を行いたいです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    /* ↓プロセスの起動を確認するために用いたコード */
    FILE *fp;
    if((fp=fopen("./test.txt","a"))==NULL) {
        return 1;
    }
    fputs("opended!", fp);
    fclose(fp);
    /* ↑プロセスの起動を確認するために用いたコード */
    while(1){
        char buf[1024];
    /* ↓パイプからの入力を確認するために用いたコード */
        fgets(buf,1024,stdin);
        if((fp=fopen("./test.txt","a"))==NULL) {
            return 1;
        }
        fputs(buf, fp);
        fclose(fp);
    /* ↑パイプからの入力を確認するために用いたコード */
        if(buf[strlen(buf)-1] != '\n'){
            while(getchar() != '\n');
        }
        printf("Status: 200 OK\r\n");
        printf("content-type: text/plain\r\n");
        printf("content-length: 12\r\n");
        printf("\r\n");
        printf("helloworld\r\n");
    }
    return 0;
}

発生している問題・エラーメッセージ

シェルから直接起動することで①が入力を受けつけ、以下の出力(以下、②と記します)
をおこなうことを確認できました。

Status: 200 OK
content-type: text/plain
content-length: 12

helloworld


①をRustのstd::process::Commandを用いて子プロセスとして起動し、標準入力から得たバイトコードを子プロセスの標準入力へ入力し、子プロセスの標準出力から得たバイトコードを文字列に変換し標準出力へ出力する処理を記述したところ、

ファイルへの出力から、プロセスの起動及びパイプからの入力を確認することができたのですが、①からの出力を読み取る処理でプロセスからの出力待ちになってしまい、②が得られませんでした。

該当のソースコード

use std::process::{Command, Stdio};
use std::io::prelude::*;

fn main() {
    let mut cmd = Command::new(
        Path::new("./a.out")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();
    let mut bytes = Vec::new();
    std::io::stdin().read(&mut bytes).unwrap();
    println!("entered!");
    let mut res = String::new();
    let out = cmd.stdout.as_mut().unwrap();
    cmd.stdin.as_mut().unwrap().write(&mut bytes).unwrap();
    println!("writed!");
    out.read_to_string(&mut res).unwrap();
    println!("read!");    // この出力が得られないことから、read_to_stringメソッドが出力を待機していると判断しました。
    println!("{}", res);
}

cmd.stdinへの入力に対する出力を受け取るにはどうすればいいのでしょうか?
プロセス間通信の心得がある方、ご教授いただけると幸いです。

試したこと

hoshi-takanoriさんのご指摘を受け、読み取り部を以下のように変更しました。

-    out.read_to_string(&mut res).unwrap();
+    let mut buf = [0; 4];
+    while let Ok(s) = out.read(&mut buf) {
+        match s {
+            0 => break,
+            4 => res.append(&mut buf.to_vec()),
+            _ => {
+                res.append(&mut buf.to_vec());
+                break;
+            }
+        }
+        println!("reading...");    // readが処理をブロックしているか確認
+    }


この場合も同様に処理がブロックされました。("reading..."という出力が一つも確認できませんでした。)

補足情報

OS: Arch Linux (カーネルバージョン5.5.8)
Rustバージョン: 1.43.0-nightly

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

ご質問のプログラムを試してみたところ、2つ問題がありました。

1つ目の問題は、Rust側のプログラムに問題があり、子プロセスの標準入力に対して空のデータ(0バイトのバイト列)がwriteされていることです。子プロセス側のCプログラムは標準入力に改行文字が送られてくることを期待していますので、子プロセスはそこで待ちに入ってしまいます。

    // バッファとして空のベクタ(長さは0)を作成
    let mut bytes = Vec::new();
    // readはバッファの長さ分のバイト列を受信する(つまり0バイト受信する)
    std::io::stdin().read(&mut bytes).unwrap();
    // ここでは子プロセスの標準入力に0バイトのデータを送信している
    cmd.stdin.as_mut().unwrap().write(&mut bytes).unwrap();

標準入力から1行読み込むなら、BufReadトレイトのread_line()メソッドが便利です。(最後に例を示します)

2つ目の問題は、CとRustは標準出力へのwriteをデフォルトでバッファリングするため、Rustのwrite()やCのprintf()でバイト列をwriteしても、いつバッファがflushされて、そのバイト列がOSに送られるのかはわからないことです。一応、改行文字を送るとflushされるという説もありますが、プログラムから明示的にflushしたほうが無難でしょう。

flushの方法ですが、Rustでは子プロセスのstdinに対してflush()を呼びます。Cではfflush(stdout)を呼びます。Cプログラムは以下のように修正します。

        printf("Status: 200 OK\r\n");
        printf("content-type: text/plain\r\n");
        printf("content-length: 12\r\n");
        printf("\r\n");
        printf("helloworld\r\n");
        // バッファを明示的にflushする
        fflush(stdout);

Rustプログラムですが、上記の修正に加えて以下のような変更をすると少しすっきりと書けます。

  • read()の代わりにread_line()を使う
  • write()の代わりにwrite!()マクロを使う
  • main()の戻り値型をResultに変えて、unwrap()の代わりに?演算子が使えるようにする

これらの修正を施すと以下のようになります。

use std::io::prelude::*;
use std::io::BufReader;
use std::path::Path;
use std::process::{Command, Stdio};

// mainの戻り値型をResultにすると、unwrap()の代わりに?演算子が使える
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::new(Path::new("./a.out"))
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;
    let mut req = String::new();
    // BufReadトレイトのread_line()メソッドを使う
    std::io::stdin().read_line(&mut req)?;

    {
        let pipe_out = cmd.stdin.as_mut()
            .ok_or_else(|| "Cannot get stdin of cmd")?;
        // write()の代わりにwrite!()マクロを使う
        write!(pipe_out, "{}", req)?;
        // バッファを明示的にflushする
        pipe_out.flush()?;
    }

    let pipe_in = cmd.stdout
        .ok_or_else(|| "Cannot get stdout of cmd")?;
    let mut reader = BufReader::new(pipe_in);
    let mut res = String::new();

    // BufReaderなら、BufReadトレイトのread_line()メソッドを使えるので便利
    while let Ok(_) = reader.read_line(&mut res) {
        println!("read: {}", res);
        res.clear();
    }
    // 子プロセスが標準出力をクローズしない限りこのコードは実行されない
    println!("done!");

    Ok(())
}

ただし、Cプログラム側が無限ループになっており、標準出力がクローズされることがありません。そのため、Rust側の"done!"は出力されず、プログラムも終了しないという問題は残ります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/03/16 14:31

    回答ありがとうございます。とても丁寧に書いて下さり助かりました!

    キャンセル

0

read_to_string は Read the entire contents of a file into a string. ということなので、ファイルの終了まで、またはパイプの場合は接続が切れる(子プロセスが標準出力を閉じるか、プロセスを終了する)まで読み込みを行います。ところが、子プロセスは無限ループなので、いつまで経っても入力待ちのままになります。

    out.read_to_string(&mut res).unwrap();

Rust の勉強中の方にこんなことを言うのは心苦しいのですが、この辺は UNIX あるいは C 言語によるシステムプログラミングの基本なので、まずはそっちの勉強が必要ではないかと…。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.58%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る