2015-07-23 21 views
7

Chcę być w stanie użyć Rust do odrodzenia powłoki podrzędnej, a następnie wielokrotnie przekazywać jej dowolne polecenia i przetwarzać ich wyniki. Znalazłem mnóstwo przykładów online pokazujących, jak przekazać pojedyncze polecenie i otrzymać jego pojedyncze wyjście, ale nie mogę tego robić wielokrotnie.Nie można potokować do lub od spawned procesu potomnego więcej niż raz

Na przykład poniższy kod zawiesza się w wierszu po komentarzu. (Wyobrażam sobie może read_to_string() blokuje dopóki nie otrzyma stdout od procesu potomnego, ale jeśli tak nie rozumiem dlaczego, że produkcja nie jest w przygotowaniu ..)

let mut child_shell = match Command::new("/bin/bash") 
    .stdin(Stdio::piped()) 
    .stdout(Stdio::piped()) 
    .spawn() 
{ 
    Err(why) => panic!("couldn't spawn child_shell: {}", Error::description(&why)), 
    Ok(process) => process, 
}; 

loop { 
    { 
     match child_shell.stdin.as_mut().unwrap().write("ls".as_bytes()) { 
      Err(why) => panic!(
       "couldn't send command to child shell: {}", 
       Error::description(&why) 
      ), 
      Ok(_) => println!("sent command to child shell"), 
     } 
    } 

    { 
     let mut s = String::new(); 
     // ↓ hangs on this line ↓ 
     match child_shell.stdout.as_mut().unwrap().read_to_string(&mut s) { 
      Err(why) => panic!("couldn't read bash stdout: {}", Error::description(&why)), 
      Ok(_) => print!("bash responded with:\n{}", s), 
     } 
    } 
} 

jestem początkujący w Rust i I uważam, że problemem jest moje ograniczone zrozumienie reguł sprawdzania i odnoszenia pożyczek, ponieważ powyższe działa dobrze (dla pojedynczej iteracji), jeśli usunę instrukcję pętli z kodu i zmienię odniesienia do początków struktury std::process::Child na niezmienne; na przykład z tego:

child_shell.stdin.as_mut().unwrap().write("ls".as_bytes()) 

do tego:

child_shell.stdin.unwrap().write("ls".as_bytes()) 

Oczywiście wielokrotnie uruchomiony ls nie jest mój ostateczny cel, i wiem, że mogę po prostu napisać skrypt, a potem mieć Rust wielokrotnie uruchom go - ale (oprócz celu, aby dowiedzieć się więcej o Rust!), jest to coś, co muszę zrobić, przynajmniej w zasadzie, dla bardziej skomplikowanego projektu (z czego jestem zadowolony, jeśli może udowadniają, że są odpowiednie dla jakichkolwiek rozwiązań, ale prawdopodobnie sposób, poza zakresem tego pytania!)

Na koniec, jeśli okaże się, że nie można w ten sposób użyć skorupy podrzędnej, mimo to chciałbym nauczyć się, jak powtarzać/ciągnąć rury do i od zarodkowanego procesu, wykonując inne arbitralne polecenie, ponieważ ja nie udało się znaleźć żadnych informacji w dokumentacji Rust, samouczkach ani w Stack Overflow.

Odpowiedz

7

read_to_string jest udokumentowana jako

Przeczytaj wszystkie bajty do tej EOF w tym źródle

Tak więc czeka aż wszyscy wejście jest zrobione, co nigdy nie nastąpi, dopóki skorupa jest zamknięta. Możesz to naprawić, odczytując ustaloną ilość danych z danych wyjściowych. Oto przykład, gdzie usunięto wszystkie błędzie piękny druk trzeba było pokazać rdzeń Rozwiązanie:

use std::process::{Command, Stdio}; 
use std::io::{BufRead, Write, BufReader}; 

fn main() { 
    let mut child_shell = Command::new("/bin/bash") 
     .stdin(Stdio::piped()) 
     .stdout(Stdio::piped()) 
     .spawn() 
     .unwrap(); 

    let child_in = child_shell.stdin.as_mut().unwrap(); 
    let mut child_out = BufReader::new(child_shell.stdout.as_mut().unwrap()); 
    let mut line = String::new(); 

    loop { 
     child_in.write("ls\n".as_bytes()).unwrap(); 
     child_out.read_line(&mut line).unwrap(); 
     println!("{}", line); 
    } 
} 

tutaj używamy cechę BufRead aby umożliwić czytanie z wejścia aż czytaliśmy jeden wiersz wartości. Następnie wydrukujemy i kontynuujemy naszą pętlę. Oczywiście istnieje więcej niż jeden wiersz wyjściowy na linię wejścia, więc będzie to po prostu coraz więcej czeka na odczyt.

W swoim prawdziwym kodzie, będziesz musiał dowiedzieć się, kiedy przerwać czytanie. Może to być naprawdę łatwe, jeśli masz odpowiedzi o stałym rozmiarze, lub naprawdę trudne, jeśli próbujesz radzić sobie z programem interaktywnym dla ludzi.

Należy zachować ostrożność, używając bezpośrednio: child_shell.stdin lub stdout, bez as_ref/as_mut. Użycie ich bezpośrednio spowoduje przeniesienie tego elementu ze struktury Child, co częściowo unieważni kod Child. Nie można już na przykład dzwonić pod numer wait lub kill.

W przypadku niepowiązanej notatki nie trzeba wywoływać takich metod, jak Error::description(&why). Możesz po prostu powiedzieć why.description().

+0

Dziękuję bardzo! Wszystkie naprawdę przydatne porady i obserwacje. Jeśli chodzi o ustalenie, kiedy przerwać czytanie z 'BufReadera', to dlaczego próbuje on po prostu wywołać' child_out.lines(). Count() 'lub' dla l in child_out.lines() 'w pętli daje mi błąd "użycia przeniesionej wartości: child_out"? Dlaczego mogę zapytać 'BufReadera' o jedną linię, ale nie dla wszystkich? Czy nie rozumiem, jak działają tutaj iteratory? – John

+0

@John nie jak działają iteratory, ale jak działa parametr 'self' dla tych metod. Sprawdź ['count'] (http://doc.rust-lang.org/std/iter/trait.Iterator.html#method.count), aby zobaczyć, że bierze on' self' według wartości, która zużywa podstawowy iterator , tak samo jak ['lines'] (http://doc.rust-lang.org/std/io/trait.BufRead.html#method.lines). Wywołanie 'count' nie ma sensu, ponieważ trzeba by przeczytać * wszystkie * dane wyjściowe procesu, aby uzyskać liczbę, do której wszystkie dane zostałyby wyrzucone. – Shepmaster

Powiązane problemy