2016-12-07 15 views
16

Làm cách nào để hợp nhất quá trình xử lý con và stderr?Hợp nhất quy trình con stdout và stderr

Sau đây không hoạt động kể từ khi sở hữu không thể được chia sẻ giữa stdoutstderr:

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

Nói cách khác, tương đương với Rust của 2>&1 trong vỏ là bao nhiêu?

+1

Tôi đang tìm kiếm thứ gì đó như 'let pipe2 = Stdio :: from_raw_fd (pipe.to_raw_fd())', nhưng tôi không thể thấy việc triển khai 'AsRawFd' trong tài liệu, khó chịu. –

+0

Tôi không chắc chắn nếu 'Stdio :: piped()' là những gì bạn cần, nhưng tại sao không chỉ sử dụng '.stdout (Stdio :: piped()). Stderr (Stdio :: piped())'? [RustByExample] (http://rustbyexample.com/std_misc/process/pipe.html) thực hiện việc này. – Manishearth

+0

@Manishearth Điều đó nghe có vẻ như nó sẽ chuyển hướng stdin và stdout đến hai * riêng biệt * ống.OP có thể muốn chuyển hướng chúng đến cùng một đường ống mà không làm mất thông tin đặt hàng giữa các nội dung của chúng. Ví dụ, cấu trúc shell Bourne tương đương, 'output = $ (command 2> & 1)'. – user4815162342

Trả lời

2

Tôi không thấy gì trong thư viện chuẩn thực hiện điều này cho bạn. Không có nghĩa là bạn không thể tự viết nó. Điều này cũng có nghĩa là bạn có thể quyết định tần suất mỗi bộ mô tả tệp được đọc và cách kết hợp dữ liệu từ mỗi bộ mô tả tệp. Ở đây, tôi cố gắng đọc theo các khối bằng cách sử dụng kích thước mặc định là BufReader và muốn đặt dữ liệu stdout đầu tiên khi cả hai trình mô tả có dữ liệu.

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)) 
} 

Khoảng cách lớn nhất là khi quá trình đã thoát. Tôi ngạc nhiên vì thiếu một phương pháp liên quan trên Child.

Xem thêm How do I prefix Command stdout with [stdout] and [sterr]?


Trong giải pháp này, không có bất kỳ trật tự nội tại giữa các file descriptor. Tương tự, hãy tưởng tượng hai xô nước. Nếu bạn bỏ trống một thùng và sau đó thấy rằng nó đã được lấp đầy lần nữa, bạn biết xô thứ hai đến sau lần đầu tiên. Tuy nhiên, nếu bạn làm trống hai nhóm và quay lại sau và cả hai đều được lấp đầy, bạn không thể cho biết nhóm nào đã được điền trước.

"Chất lượng" xen kẽ là vấn đề tần suất bạn đọc từ mỗi bộ mô tả tệp và bộ mô tả tệp nào được đọc trước tiên. Nếu bạn đọc một byte đơn từ mỗi vòng trong một vòng lặp rất chặt chẽ, bạn có thể nhận được kết quả hoàn toàn bị cắt xén nhưng đây sẽ là "chính xác" nhất liên quan đến đặt hàng. Tương tự như vậy, nếu một chương trình in "A" để stderr sau đó "B" để stdout nhưng vỏ đọc từ stdout trước stderr, sau đó kết quả sẽ là "BA", trông ngược.

+1

Giải pháp được cung cấp trong câu trả lời không tương đương với chuyển hướng kiểu '2> & 1' vì nó mất thông tin đặt hàng (được mô tả chính xác trong câu trả lời) . Trong Rust không ổn định, có một phương thức 'before_exec' có thể được sử dụng để thực hiện' unsafe {libc :: dup2 (1, 2)} '- ngoại trừ việc đó sẽ chỉ hoạt động trên các nền tảng giống Unix. – user4815162342

+0

Vâng thứ tự có thể không hoàn toàn xác định trong trường hợp chung nhưng hãy xem xét trường hợp khi tất cả đầu ra được tạo ra trên một luồng đơn trong tiến trình con. Trong trường hợp này, sẽ có thứ tự nghiêm ngặt giữa tất cả "printfs" trong chương trình. Thông tin đặt hàng này bị mất khi printfs in ra các đường ống khác nhau nhưng nếu chúng in cùng một đường ống thì bạn sẽ luôn có thể biết thứ tự các thông điệp được in. –

7

Tôi đang làm việc trên thư viện có tên duct giúp việc này trở nên dễ dàng. Nó không phải hoàn toàn ổn định bài viết nào, và không ghi nhận (mặc dù nó bản đồ rất chặt chẽ với Python version), nhưng nó hoạt động ngày hôm nay:

#[macro_use] 
extern crate duct; 

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

Các "đúng cách" để làm một cái gì đó như thế này, mà duct đang làm cho bạn theo bao gồm, là tạo ra một hệ điều hành ống đôi đã kết thúc và vượt qua đầu ghi của nó cho cả hai stdout và stderr. Lớp Command của thư viện chuẩn hỗ trợ loại điều này nói chung vì Stdio triển khai FromRawFd, nhưng tiếc là thư viện chuẩn không vạch trần cách tạo đường ống. Tôi đã viết một thư viện khác có tên là os_pipe để làm điều này bên trong duct và nếu bạn muốn bạn có thể sử dụng nó trực tiếp.

Điều này đã được thử nghiệm trên Linux, Windows và macOS.

Các vấn đề liên quan