2016-12-07 3 views
16

Wie füge ich Child Prozess Stdout und Stderr zusammen?Verschmelzen Kind Prozess Stdout und Stderr

Die folgenden seit Eigentum freigegeben werden können nicht funktioniert nicht zwischen stdout und stderr:

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

Mit anderen Worten, was ist das Äquivalent von Rust 2>&1 in der Schale?

+1

Ich war für so etwas wie 'lassen PIPE2 = Stdio :: from_raw_fd (pipe.to_raw_fd())' suchen, aber ich kann nicht eine Implementierung von 'AsRawFd' in der Dokumentation, annoyingly sehen. –

+0

Ich bin nicht sicher, ob 'Stdio :: piped()' das ist, was Sie brauchen, aber warum nicht einfach '.stdout (Stdio :: piped()) verwenden. Stderr (Stdio :: piped())'? [RustByExample] (http://rustbyexample.com/std_misc/process/pipe.html) tut dies. – Manishearth

+0

@Manishearth Das klingt, als würde es stdin und stdout auf zwei * separate * rohre umleiten.Das OP möchte sie vielleicht auf die gleiche Leitung umleiten, ohne die Bestellinformationen zwischen ihren Inhalten zu verlieren. Das entsprechende Bourne-Shell-Konstrukt ist zum Beispiel "output = $ (command 2> & 1)". – user4815162342

Antwort

2

Ich sehe nichts in der Standardbibliothek, die das für Sie tut. Bedeutet nicht, dass du es nicht selbst schreiben kannst. Dies bedeutet auch, dass Sie entscheiden müssen, wie häufig jeder Dateideskriptor gelesen wird und wie die Daten von jedem der Dateideskriptoren kombiniert werden. Hier versuche ich, Chunks mit der Standardgröße BufReader einzulesen und bevorzuge es, stdout-Daten zuerst zu setzen, wenn beide Deskriptoren Daten haben.

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

Die größte Lücke ist, wenn der Prozess beendet wurde. Ich bin überrascht über das Fehlen einer relevanten Methode auf Child.

auch bei dieser Lösung How do I prefix Command stdout with [stdout] and [sterr]?


See, gibt es keine intrinsische Ordnung zwischen den Datei-Deskriptoren. Stellen Sie sich als Analogie zwei Eimer Wasser vor. Wenn Sie einen Eimer leeren und später sehen, dass er wieder gefüllt ist, wissen Sie, dass der zweite Eimer nach dem ersten gekommen ist. Wenn Sie jedoch zwei Eimer leeren und später wiederkommen und beide gefüllt sind, können Sie nicht erkennen, welcher Eimer zuerst gefüllt wurde.

Die "Qualität" des Interleavings hängt davon ab, wie häufig Sie von jedem Dateideskriptor lesen und welcher Dateideskriptor zuerst gelesen wird. Wenn Sie in einer sehr engen Schleife ein einzelnes Byte aus jedem lesen, könnten Sie völlig verzerrte Ergebnisse erhalten, aber diese wären hinsichtlich der Reihenfolge am "genauesten". Wenn ein Programm "A" in stderr ausgibt, dann "B" in stdout, aber die Shell liest von stdout vor stderr, dann wäre das Ergebnis "BA", das rückwärts schaut.

+1

Die in der Antwort angegebene Lösung entspricht nicht der Umleitung "2> & 1", da sie Bestellinformationen verliert (wie in der Antwort richtig beschrieben). . In unstable Rust gibt es eine 'before_exec'-Methode, mit der man' unsafe {libc :: dup2 (1, 2)}} ausführen kann - außer dass das nur auf Unix-ähnlichen Plattformen funktioniert. – user4815162342

+0

Ja, die Reihenfolge mag im allgemeinen Fall nicht vollständig deterministisch sein, aber beachte den Fall, wenn die gesamte Ausgabe in einem einzelnen Thread im Kindprozess generiert wird. In diesem Fall wird die Reihenfolge zwischen allen "printfs" im Programm streng sein. Diese Bestellinformationen gehen verloren, wenn die printfs zu verschiedenen Pipes drucken, aber wenn sie auf die gleiche Pipe drucken, können Sie immer die Reihenfolge erkennen, in der die Nachrichten gedruckt wurden. –

7

Ich arbeite an einer Bibliothek namens duct, die dies einfach macht. Es ist nicht völlig stabil noch, und nicht dokumentiert (obwohl es sehr eng Karten zum Python version), aber es funktioniert heute:

#[macro_use] 
extern crate duct; 

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

der „richtigen Weg“, so etwas zu tun, was duct für Dich tut, unter Die Abdeckungen sollen eine doppelendige OS-Pipe erzeugen und das Schreibende davon sowohl an stdout als auch an stderr übergeben. Die Command Klasse Standardbibliothek unterstützt diese Art der Sache in der Regel, weil Stdio Geräte FromRawFd, aber leider ist die Standard-Bibliothek verfügbar macht keinen Weg Rohre zu schaffen. Ich habe eine andere Bibliothek namens os_pipe geschrieben, um dies innerhalb von duct zu tun, und wenn Sie möchten, können Sie es direkt verwenden.

Dies wurde unter Linux, Windows und MacOS getestet.