如何使用 vala/glib 传输到进程

How to pipe to a process using vala/glib

我正在尝试使用 GLib 的 spawn_command_line_sync 方法将 echo 的输出通过管道传输到命令中。我遇到的问题 运行 是 echo 将整个命令解释为参数。

为了更好地解释,我 运行 在我的代码中这样做:

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

我希望变量被回显到管道,命令 运行 和数据通过管道传输,但是当我检查结果时,它只是在 echo 之后回显所有内容,如下所示:

"some_var's value" | command

我想我可以只使用 Posix class 到 运行 命令,但我喜欢有结果、错误和状态值来听 spawn_command_line_sync 方法提供。

问题是您正在为内核的 exec() 系统调用提供 shell 语法。 shell 管道运算符将一个进程的标准输出重定向到下一个进程的标准输入。要使用 Vala 实现它,您需要获取 command 进程的标准输入的文件描述符,您是 运行,然后手动写入 some_var

这是完整的工作实施:

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 */
}

我想使用 FileStream 是真正让人难以理解的原因。结果非常简单。

您正在将两个子流程合并为一个。相反 echocommand 应该分开处理,并在它们之间设置管道。由于某些原因,Stack Overflow 和其他站点上的许多示例都使用 Process.spawn_* 函数,但使用 GSubprocess 是一种更简单的语法。

此示例将 find . 的输出通过管道传输到 sort,然后将输出打印到控制台。该示例稍长一些,因为它是一个完整的示例,并且使用 GMainContext 进行异步调用。 GMainContext 被 GMainLoop、GApplication 和 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);
}

splice_subprocesses 函数可以回答您的问题。它将第一个命令的 STDOUT 作为 InputStream 并将其与第二个命令的 OutputStream (STDIN) 拼接。

read_piped_commands函数从管道的末端获取输出。这是一个 InputStream,已包装在 DataInputStream 中以提供对 read_line_async 便捷方法的访问。

根据之前的回答,一个有趣的案例可能是使用程序参数让通用应用程序通过管道传输其上的任何输入:

pipe.vala:

void main (string[] args) {
    try {
        string command = args[1];
        var subproc = new Subprocess(STDIN_PIPE | STDOUT_PIPE, command);

        var data = args[2].data;
        var input = new MemoryInputStream.from_data(data, GLib.free);

        subproc.get_stdin_pipe ().splice (input, CLOSE_TARGET);
        var end_pipe = subproc.get_stdout_pipe ();
        var output = new DataInputStream (end_pipe);

        string? line = null;
        do {
            line = output.read_line();
            print (@"$(line ?? "")\n");
        } while (line != null);
    } catch (Error error) {
        print (@"Error: $(error.message)\n");
    }
}

构建:

$ valac --pkg gio-2.0 pipe.vala

和运行:

$ ./pipe sort "cc
ab
aa
b
"

输出:

aa
ab
b
cc