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

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

新規登録して質問してみよう
ただいま回答率
85.49%
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

2回答

3727閲覧

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

ta1g3n

総合スコア35

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

0グッド

0クリップ

投稿2020/03/15 17:00

編集2020/03/15 19:28

前提・実現したいこと

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

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4 5int main() { 6 /* ↓プロセスの起動を確認するために用いたコード */ 7 FILE *fp; 8 if((fp=fopen("./test.txt","a"))==NULL) { 9 return 1; 10 } 11 fputs("opended!", fp); 12 fclose(fp); 13 /* ↑プロセスの起動を確認するために用いたコード */ 14 while(1){ 15 char buf[1024]; 16 /* ↓パイプからの入力を確認するために用いたコード */ 17 fgets(buf,1024,stdin); 18 if((fp=fopen("./test.txt","a"))==NULL) { 19 return 1; 20 } 21 fputs(buf, fp); 22 fclose(fp); 23 /* ↑パイプからの入力を確認するために用いたコード */ 24 if(buf[strlen(buf)-1] != '\n'){ 25 while(getchar() != '\n'); 26 } 27 printf("Status: 200 OK\r\n"); 28 printf("content-type: text/plain\r\n"); 29 printf("content-length: 12\r\n"); 30 printf("\r\n"); 31 printf("helloworld\r\n"); 32 } 33 return 0; 34}

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

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

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

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

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

該当のソースコード

Rust

1use std::process::{Command, Stdio}; 2use std::io::prelude::*; 3 4fn main() { 5 let mut cmd = Command::new( 6 Path::new("./a.out") 7 .stdin(Stdio::piped()) 8 .stdout(Stdio::piped()) 9 .spawn() 10 .unwrap(); 11 let mut bytes = Vec::new(); 12 std::io::stdin().read(&mut bytes).unwrap(); 13 println!("entered!"); 14 let mut res = String::new(); 15 let out = cmd.stdout.as_mut().unwrap(); 16 cmd.stdin.as_mut().unwrap().write(&mut bytes).unwrap(); 17 println!("writed!"); 18 out.read_to_string(&mut res).unwrap(); 19 println!("read!"); // この出力が得られないことから、read_to_stringメソッドが出力を待機していると判断しました。 20 println!("{}", res); 21}

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

試したこと

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

Diff

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

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

補足情報

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

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

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

rust

1 // バッファとして空のベクタ(長さは0)を作成 2 let mut bytes = Vec::new(); 3 // readはバッファの長さ分のバイト列を受信する(つまり0バイト受信する) 4 std::io::stdin().read(&mut bytes).unwrap(); 5 // ここでは子プロセスの標準入力に0バイトのデータを送信している 6 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プログラムは以下のように修正します。

c

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

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

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

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

rust

1use std::io::prelude::*; 2use std::io::BufReader; 3use std::path::Path; 4use std::process::{Command, Stdio}; 5 6// mainの戻り値型をResultにすると、unwrap()の代わりに?演算子が使える 7fn main() -> Result<(), Box<dyn std::error::Error>> { 8 let mut cmd = Command::new(Path::new("./a.out")) 9 .stdin(Stdio::piped()) 10 .stdout(Stdio::piped()) 11 .spawn()?; 12 let mut req = String::new(); 13 // BufReadトレイトのread_line()メソッドを使う 14 std::io::stdin().read_line(&mut req)?; 15 16 { 17 let pipe_out = cmd.stdin.as_mut() 18 .ok_or_else(|| "Cannot get stdin of cmd")?; 19 // write()の代わりにwrite!()マクロを使う 20 write!(pipe_out, "{}", req)?; 21 // バッファを明示的にflushする 22 pipe_out.flush()?; 23 } 24 25 let pipe_in = cmd.stdout 26 .ok_or_else(|| "Cannot get stdout of cmd")?; 27 let mut reader = BufReader::new(pipe_in); 28 let mut res = String::new(); 29 30 // BufReaderなら、BufReadトレイトのread_line()メソッドを使えるので便利 31 while let Ok(_) = reader.read_line(&mut res) { 32 println!("read: {}", res); 33 res.clear(); 34 } 35 // 子プロセスが標準出力をクローズしない限りこのコードは実行されない 36 println!("done!"); 37 38 Ok(()) 39}

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

投稿2020/03/16 02:07

編集2020/03/16 02:10
tatsuya6502

総合スコア2035

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ta1g3n

2020/03/16 05:31

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

0

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

rust

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

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

投稿2020/03/15 18:18

hoshi-takanori

総合スコア7895

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問