最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Gnome shell extensions: how to run a command with pipes - Stack Overflow

programmeradmin1浏览0评论

So I'm making a Gnome Shell extension. And I want to be able to run some mand with a pipe. (The mand is actually "xrandr --query | awk 'something'", but that is off topic)

So, what I have done so far is

GLib.spawn_async_with_pipes(null,
                            ['/usr/bin/xrandr', '--query', '|', 'awk...'], null,
                            GLib.SpawnFlags.DO_NOT_REAP_CHILD, null);

But it doesn't work! I can't find any example of running a mand in a gnome extensions with a pipe.

Do I have to write "|" in the mand like I did ?

So I'm making a Gnome Shell extension. And I want to be able to run some mand with a pipe. (The mand is actually "xrandr --query | awk 'something'", but that is off topic)

So, what I have done so far is

GLib.spawn_async_with_pipes(null,
                            ['/usr/bin/xrandr', '--query', '|', 'awk...'], null,
                            GLib.SpawnFlags.DO_NOT_REAP_CHILD, null);

But it doesn't work! I can't find any example of running a mand in a gnome extensions with a pipe.

Do I have to write "|" in the mand like I did ?

Share Improve this question edited Feb 9, 2016 at 20:05 gpoo 8,6483 gold badges40 silver badges53 bronze badges asked Nov 25, 2015 at 8:36 S_DS_D 611 silver badge3 bronze badges 6
  • Can include link to spawn_async_with_pipes documentation ? Tried without mas ['/usr/bin/xrandr --query | awk...'] ? – guest271314 Commented Nov 25, 2015 at 10:16
  • Doc is there Without mas it doesn't works at all. – S_D Commented Nov 25, 2015 at 10:44
  • Why not place all your pipes in a bash script, and then use the bash script directly? – tucuxi Commented Nov 25, 2015 at 10:54
  • Tried with full path to awk ? " By default, the name of the program must be a full path." valadoc/#!api=glib-2.0/GLib.Process.spawn_async_with_pipes – guest271314 Commented Nov 25, 2015 at 11:17
  • 1 You need to call spawn_async_with_pipes twice, once for the xrandr mand and once for the awk mand. Capture the standard out of the first, and pass that as an argument to the second. You can't run a shell pipeline by using | as an argument. – chepner Commented Nov 25, 2015 at 14:38
 |  Show 1 more ment

3 Answers 3

Reset to default 3

spawn_async_with_pipes doesn't do what you want (in a simple way). It return the pipes to process with it. You could do it with two call and connecting but it will be a little bit plex.

A simple way to keep the exact syntax is to call a shell which will do the pipe processing with the help of this answer which give a way to call a mand, I wrote the following code which call the shell (bash for this case) with correct arguments

const Util = imports.misc.util;
Util.spawn(['/bin/bash', '-c', "xrandr --query | awk 'something'"])

I implemented a TerminalReader class some time ago in a Cinnamon Applet: https://github./lestcape/Configurable-Menu/blob/OwnAPI/configurableMenu%40lestcape/pakagesManager.js#L31

This class is now used in other places also, so you have more examples to underestand it better: https://github./search?l=JavaScript&q=TerminalReader&type=Code&utf8=%E2%9C%93

Here is the source code of the class:

function TerminalReader() {
    this._init.apply(this, arguments);
}

TerminalReader.prototype = {
_init: function(mand, callback) {
    this._callbackPipe = callback;
    this._mandPipe = mand;
    this.idle = true;
    this._childWatch = null;
},

executeReader: function() {
    if(this.idle) {
        this.idle = false;
        try {
            let [success, argv] = GLib.shell_parse_argv("sh -c '" + this._mandPipe + "'");
            if(success) {
                let [exit, pid, stdin, stdout, stderr] =
                  GLib.spawn_async_with_pipes(
                      null, // cwd
                      argv, // args
                      null, // env
                      GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, //Use env path and no repet
                      null // child_setup
                  );

                this._childPid = pid;
                this._stdin = new Gio.UnixOutputStream({ fd: stdin, close_fd: true });
                this._stdout = new Gio.UnixInputStream({ fd: stdout, close_fd: true });
                this._stderr = new Gio.UnixInputStream({ fd: stderr, close_fd: true });

                // We need this one too, even if don't actually care of what the process
                // has to say on stderr, because otherwise the fd opened by g_spawn_async_with_pipes
                // is kept open indefinitely
                this._stderrStream = new Gio.DataInputStream({ base_stream: this._stderr });
                this._dataStdout = new Gio.DataInputStream({ base_stream: this._stdout });
                this._cancellableStderrStream = new Gio.Cancellable();
                this._cancellableStdout = new Gio.Cancellable();

                this.resOut = 1;
                this._readStdout();
                this.resErr = 1;
                this._readStderror();

                this._childWatch = GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, Lang.bind(this, function(pid, status, requestObj) {
                    GLib.source_remove(this._childWatch);
                    this._childWatch = null;
                    this._stdin.close(null);
                    this.idle = true;
                }));
            }
            //throw
        } catch(err) {
            if(err.code == GLib.SpawnError.G_SPAWN_ERROR_NOENT) {
                err.message = _("Command not found.");
            } else {
                // The exception from gjs contains an error string like:
                //   Error invoking GLib.spawn_mand_line_async: Failed to
                //   execute child process "foo" (No such file or directory)
                // We are only interested in the part in the parentheses. (And
                // we can't pattern match the text, since it gets localized.)
                err.message = err.message.replace(/.*\((.+)\)/, '$1');
            }
            throw err;
        }
    }
},

destroy: function() {
    try {
        if(this._childWatch) {
            GLib.source_remove(this._childWatch);
            this._childWatch = null;
        }
        if(!this._dataStdout.is_closed()) {
            this._cancellableStdout.cancel();
            this._stdout.close_async(0, null, Lang.bind(this, this.closeStdout));
        }
        if(!this._stderrStream.is_closed()) {
            this._cancellableStderrStream.cancel();
            this._stderrStream.close_async(0, null, Lang.bind(this, this.closeStderrStream));
        }
        this._stdin.close(null);
        this.idle = true;
    }
    catch(e) {
        Main.notify("Error on close" + this._dataStdout.is_closed(), e.message);
    }
},

closeStderrStream: function(std, result) {
    try {
        std.close_finish(result);
    } catch(e) {
        std.close_async(0, null, Lang.bind(this, this.closeStderrStream));
    }
},

closeStdout: function(std, result) {
    try {
        std.close_finish(result);
    } catch(e) {
        std.close_async(0, null, Lang.bind(this, this.closeStderrStream));
    }
},

_readStdout: function() {
    this._dataStdout.fill_async(-1, GLib.PRIORITY_DEFAULT, this._cancellableStdout, Lang.bind(this, function(stream, result) {
        try {
            if(!this._dataStdout.is_closed()) {
                if(this.resOut != -1)
                    this.resOut = this._dataStdout.fill_finish(result);// end of file
                if(this.resOut == 0) {
                    let val = stream.peek_buffer().toString();
                    if(val != "")
                        this._callbackPipe(this._mandPipe, true, val);
                    this._stdout.close(this._cancellableStdout);
                } else {
                    // Try to read more
                    this._dataStdout.set_buffer_size(2 * this._dataStdout.get_buffer_size());
                    this._readStdout();
                }
            }
        } catch(e) {
            global.log(e.toString());
        }
    }));
},

_readStderror: function() {
    this._stderrStream.fill_async(-1, GLib.PRIORITY_DEFAULT, this._cancellableStderrStream, Lang.bind(this, function(stream, result) {
        try {
            if(!this._stderrStream.is_closed()) {
                if(this.resErr != -1)
                    this.resErr = this._stderrStream.fill_finish(result);
                if(this.resErr == 0) { // end of file
                    let val = stream.peek_buffer().toString();
                    if(val != "")
                        this._callbackPipe(this._mandPipe, false, val);
                    this._stderr.close(null);
                } else {
                    this._stderrStream.set_buffer_size(2 * this._stderrStream.get_buffer_size());
                    this._readStderror();
                }
            }
        } catch(e) {
            global.log(e.toString());
        }
    }));
}
};

calling spawn_async_with_pipes() isn't going to help you, as it gives you back an object with availlable pipes for stdin/stdout/stderr rather than giving you calls that are piped to one another. Except for just calling a shell instance and letting it execute mands, the only way is to stick to the extension itself, letting GNOME handle everything using a temp file and default shell (one that overwrites itself if it already exists).

For the example code, lets do the same as journalctl | grep -i js would do on a CLI:

//Gio for File I/O and GLib for mand execution
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
//Create an object for the temp file
let file = Gio.file_new_tmp(null);
//Now write the output of journalctl to the file
file[1].get_output_stream().write(GLib.spawn_mand_line_sync("journalctl")[1], null);
//Execute the mand and save the result, this locks the thread though,
//so don't use indefinite mands like journalctl -f
//This returns [Boolean success, String stdout, String stderr, Number exit_status]
let journalJS = GLib.spawn_mand_line_sync('grep -i js ' + file[0].get_path())[1]

Now feel free to do with the data you have, as everything exits after the mand is finished.

Don't forget to close the file using file[1].close(); and setting any remaining variables to null when finished.

发布评论

评论列表(0)

  1. 暂无评论