2016-12-07 8 views
16

子プロセスstdoutとstderrをマージするにはどうすればよいですか?所有権がstdoutstderrの間で共有することができないので、子プロセスstdoutとstderrをマージする

次は動作しません:

つまり
let pipe = Stdio::piped(); 
let prog = Command::new("prog") 
         .stdout(pipe) 
         .stderr(pipe) 
         .spawn() 
         .expect("failed to execute prog"); 

、シェル内の2>&1の錆と同等とは何でしょうか?

+1

私は 'let pipe2 = Stdio :: from_raw_fd(pipe.to_raw_fd())'のようなものを探していましたが、ドキュメンテーションの 'AsRawFd'の実装が気にならない。 –

+0

'Stdio :: piped()'が必要なのかどうか分かりませんが、 '.stdout(Stdio :: piped())。stderr(Stdio :: piped())'を使うのはなぜですか? [RustByExample](http://rustbyexample.com/std_misc/process/pipe.html)がこれを行います。 – Manishearth

+0

@Manishearthこれはstdinとstdoutを2つの別々のパイプにリダイレクトするように聞こえます。OPは、その内容の間で注文情報を失うことなく、それらを同じパイプにリダイレクトしたいかもしれません。同等のBourneシェル構造は、例えば、 'output = $(command 2>&1)'です。 – user4815162342

答えて

2

私はこれを行う標準ライブラリに何も表示されません。自分で書くことができないというわけではありません。これはまた、各ファイルディスクリプタの読み取り頻度や、各ファイルディスクリプタのデータの結合方法を決定することを意味します。ここでは、デフォルトのBufReaderサイズを使用してチャンクを読み込み、両方のディスクリプタにデータがある場合は、最初にstdoutデータを格納します。

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

fn main() { 
    let mut child = 
     Command::new("/tmp/output") 
     .stdout(Stdio::piped()) 
     .stderr(Stdio::piped()) 
     .spawn() 
     .expect("Couldn't run program"); 

    let mut output = Vec::new(); 

    // Should be moved to a function that accepts something implementing `Write` 
    { 
     let stdout = child.stdout.as_mut().expect("Wasn't stdout"); 
     let stderr = child.stderr.as_mut().expect("Wasn't stderr"); 

     let mut stdout = BufReader::new(stdout); 
     let mut stderr = BufReader::new(stderr); 

     loop { 
      let (stdout_bytes, stderr_bytes) = match (stdout.fill_buf(), stderr.fill_buf()) { 
       (Ok(stdout), Ok(stderr)) => { 
        output.write_all(stdout).expect("Couldn't write"); 
        output.write_all(stderr).expect("Couldn't write"); 

        (stdout.len(), stderr.len()) 
       } 
       other => panic!("Some better error handling here... {:?}", other) 
      }; 

      if stdout_bytes == 0 && stderr_bytes == 0 { 
       // Seems less-than-ideal; should be some way of 
       // telling if the child has actually exited vs just 
       // not outputting anything. 
       break; 
      } 

      stdout.consume(stdout_bytes); 
      stderr.consume(stderr_bytes); 
     } 
    } 

    let status = child.wait().expect("Waiting for child failed"); 
    println!("Finished with status {:?}", status); 
    println!("Combined output: {:?}", std::str::from_utf8(&output)) 
} 

プロセスがいつ終了するかは、最も大きなギャップです。私はChildに関連する方法がないことに驚いています。

は、このソリューションでもHow do I prefix Command stdout with [stdout] and [sterr]?


を参照してください、ファイル・ディスクリプタ間のいずれかの本質的な順序はありません。類推として、2つのバケツの水を想像してみてください。バケツを空にして後でそれが再び満たされていることがわかると、2番目のバケツが最初のバケットの後に来ることがわかります。ただし、2つのバケットを空にして後で戻ってきて、両方がいっぱいになると、どのバケットが最初に満たされたかを知ることができません。

インターリーブの「品質」は、各ファイルディスクリプタから読み取る頻度と最初に読み取られるファイルディスクリプタの問題です。非常にタイトなループで各バイトから1バイトを読み取ると、完全に文字化けした結果が得られるかもしれませんが、これは注文に関して最も正確です。同様に、プログラムがstderrに "A"を、そしてstdoutに "B"を出力するが、シェルがstderrの前にstdoutから読み込むと、結果は "BA"になり、逆向きになる。

+1

解決策で提供される解決策は、注文情報が失われるため(回答に正しく記述されているように)、 '2>&1'形式のリダイレクトと同じではありません。 。不安定なRustには、 '安全ではない{libc :: dup2(1,2)}'を実行するために使うことができる 'before_exec'メソッドがありますが、これはUnixライクなプラットフォーム上でしか動作しません。 – user4815162342

+0

ええ、注文は一般的なケースでは完全に確定的ではないかもしれませんが、すべての出力が子プロセスの単一のスレッドで生成される場合を考慮してください。この場合、プログラム内のすべての "printfs"の間で厳密な順序付けが行われます。この注文情報は、printfsが別のパイプに印刷するときに失われますが、同じパイプに印刷すると、メッセージが印刷された順序を常に知ることができます。 –

7

私はductという名前のライブラリで作業しています。 (それはPython versionに非常に密接にマップするが)それは文書化され、完全に安定していて、いないではないが、それは今日の作品:

#[macro_use] 
extern crate duct; 

fn main() { 
    cmd!("echo", "hi").stderr_to_stdout().run(); 
} 

ductは、下のあなたのためにやっているこのような何かを行うには「正しい方法」、カバーは、ダブルエンドのOSパイプを作成し、その書き込み側をstdoutとstderrの両方に渡すことです。 StdioFromRawFdを実装しているため、標準ライブラリのCommandクラスは一般的にこのようなことをサポートしていますが、残念なことに標準ライブラリはパイプを作成する方法を公開していません。私はductの中でこれを行うためにos_pipeという別のライブラリを書いています。あなたが望むなら、それを直接使うことができます。

これは、Linux、Windows、およびMacOSでテストされています。

関連する問題