J'essaye de canaliser la sortie de l'écho dans une commande en utilisant la méthode spawn_command_line_sync de GLib. Le problème que j'ai rencontré est que l'écho interprète la commande entière comme argument.

Pour mieux expliquer, j'exécute ceci dans mon code:

string command = "echo \"" + some_var + "\" | command";
Process.spawn_command_line_sync (command.escape (), 
                                 out r, out e, out s);

Je m'attendrais à ce que la variable soit renvoyée en écho au tube et que la commande s'exécute avec les données acheminées, mais lorsque je vérifie le résultat, cela fait simplement écho à tout après l'écho comme ceci:

"some_var's value" | command

Je pense que je pourrais simplement utiliser la classe Posix pour exécuter la commande mais j'aime avoir les valeurs de résultat, d'erreur et de statut à écouter que la méthode spawn_command_line_sync fournit.

3
Plays2 26 janv. 2019 à 21:45

3 réponses

Meilleure réponse

Le problème est que vous fournissez une syntaxe shell à ce qui est essentiellement l'appel système exec() du noyau. L'opérateur de canal de shell redirige le stdout d'un processus vers le stdin du suivant. Pour implémenter cela en utilisant Vala, vous devez obtenir le descripteur de fichier pour le stdin du processus command que vous exécutez, et y écrire some_var manuellement.

4
Philip Withnall 26 janv. 2019 à 19:57

Voici l'implémentation complète et fonctionnelle:

try {
    string[] command = {"command", "-options", "-etc"};
    string[] env = Environ.get ();
    Pid child_pid;
    string some_string = "This is what gets piped to stdin"

    int stdin;
    int stdout;
    int stderr;

    Process.spawn_async_with_pipes ("/",
        command,
        env,
        SpawnFlags.SEARCH_PATH | SpawnFlags.DO_NOT_REAP_CHILD,
        null,
        out child_pid,
        out stdin,
        out stdout,
        out stderr);

    FileStream input = FileStream.fdopen (stdin, "w");
    input.write (some_string.data);

    /* Make sure we close the process using it's pid */
    ChildWatch.add (child_pid, (pid, status) => {
        Process.close_pid (pid);
    });
} catch (SpawnError e) {
    /* Do something w the Error */
}

Je suppose que jouer avec FileStream est ce qui a vraiment rendu difficile de comprendre cela. S'est avéré être assez simple.

3
Plays2 26 janv. 2019 à 20:36

Vous combinez deux sous-processus en un seul. Au lieu de cela, echo et command doivent être traités séparément et avoir un tube installé entre eux. Pour une raison quelconque, de nombreux exemples sur Stack Overflow et d'autres sites utilisent les fonctions Process.spawn_*, mais l'utilisation de GSubprocess est une syntaxe plus simple.

Cet exemple dirige la sortie de find . vers sort, puis imprime la sortie vers la console. L'exemple est un peu plus long car il s'agit d'un exemple pleinement fonctionnel et utilise un GMainContext pour les appels asynchrones. GMainContext est utilisé par GMainLoop, GApplication et GtkApplication:

void main () {
    var mainloop = new MainLoop ();
    SourceFunc quit = ()=> {
        mainloop.quit ();
        return Source.REMOVE;
    };
    read_piped_commands.begin ("find .", "sort", quit);
    mainloop.run ();
}

async void read_piped_commands (string first_command, string second_command, SourceFunc quit) {
    var output = splice_subprocesses (first_command, second_command);
    try {
        string? line = null;
        do {
            line = yield output.read_line_async ();
            print (@"$(line ?? "")\n");
            }
        while (line != null);
    } catch (Error error) {
        print (@"Error: $(error.message)\n");
    }
    quit ();
}

DataInputStream splice_subprocesses (string first_command, string second_command) {
    InputStream end_pipe = null;
    try {
        var first = new Subprocess.newv (first_command.split (" "), STDOUT_PIPE);
        var second = new Subprocess.newv (second_command.split (" "), STDIN_PIPE | STDOUT_PIPE);

        second.get_stdin_pipe ().splice (first.get_stdout_pipe (), CLOSE_TARGET);
        end_pipe = second.get_stdout_pipe ();
    } catch (Error error) {
        print (@"Error: $(error.message)\n");
    }
    return new DataInputStream (end_pipe);
}

C'est la fonction splice_subprocesses qui répond à votre question. Il prend le STDOUT de la première commande comme un InputStream et le joint avec le OutputStream (STDIN) pour la deuxième commande.

La fonction read_piped_commands prend la sortie de la fin du tube. Il s'agit d'un InputStream qui a été enveloppé dans un DataInputStream pour donner accès à la méthode pratique read_line_async.

5
aggsol 13 mars 2020 à 12:22