Skip to content

Add Exec2, the "better version of Exec"#5103

Draft
fingolfin wants to merge 1 commit intomasterfrom
mh/Exec2
Draft

Add Exec2, the "better version of Exec"#5103
fingolfin wants to merge 1 commit intomasterfrom
mh/Exec2

Conversation

@fingolfin
Copy link
Copy Markdown
Member

(This is code I wrote some time ago, but I never found time to clean it up enough for inclusion. Due to a recent question in the GAP forum, I thought it might be a good idea to at least upload it here as a draft; perhaps someone else is interested in helping to finish it up? E.g. by coming up with a better name for the new function ;-)

Add a new function Exec2 which is as easy to use as Exec but
much more powerful and less easy to misuse:

  • don't pass everything to a shell, thus avoiding compatibility issues due to different shells on different systems (this also has one downside: you can't use redirects like ">/dev/null")
  • pass arguments as separate strings, thus avoiding any issues with quoting quotes, quoting spaces, etc.
  • return the exit code of the executed command so that one can determine its success or failure; the return value is actually a record to allow returning other data, such as the programs output
  • make it very easy to override the input and output streams
  • by default, pass no input to the program (instead of passing anything the user might type, as Exec does); this can be changed by adding an input stream to the argument list
  • by default, capture any output of the program into a string and return it, instead of just printing the output to the console; this can be changed by adding an output stream to the argument list

Comment thread lib/process.gi
Error("must specify at most one working directory");
fi;
dir := a;
elif IsInputStream(a) then
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now input and output stream can come at any position and in any order. Perhaps I should make this code stricter, and require that they come in a specific place (canonical places: 1. as the first two arguments; 2. after the command name; 3. as the last two arguments)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it would be a bigger change, I wonder if the input should be a record, with keys like input :=, output := .. this would mean in future it would be easier to do things like add stderr support, and make it clear which output stream is stdout and which is stderr, things like that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that idea. It could then also be made backwards compatible: if there is a single argument which is a record, then use the new code; otherwise call the old code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I don't like about it is that for simple uses, it is much more verbose. Compare: e.g.:

old code:

Exec( Concatenation("start ", winfilename ) );

this PR:

Exec2( "start", winfilename );

with a record (record entries subject to change, of course):

Exec(rec(cmd:= "start", args := [ winfilename ] );

A compromise would be to only use a rec for the "extra" arguments like input, output, etc., like so:

Exec2( "start", winfilename, rec( input := BLAH, output := BLEH ) );

but then of course I can't use this to differentiate old and new callers.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So here's a possible way to go:

  • allow input and output redirects only via an option record
  • the option record must come last
  • otherwise it stays as it is in this PR, that is: first the command name, then the list of arguments
    • arguments must be strings
    • for convenience it might be nice to also allow integers (and turn them into strings automatically)
    • anything else would be an error (for now, at least; we can add more things in the future this way; what I don't want is to String everything automatically, that just paints us into another corner)

Does that sound reasonable @ChrisJefferson ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds reasonable to me at least @fingolfin.

Comment thread lib/process.gi
Add a new function `Exec2` which is as easy to use as `Exec` but
much more powerful and less easy to misuse:

- don't pass everything to a shell, thus avoiding compatibility
  issues due to different shells on different systems (this also
  has one downside: you can't use redirects like ">/dev/null")
- pass arguments as separate strings, thus avoiding any issues with
  quoting quotes, quoting spaces, etc.
- return the exit code of the executed command so that one can
  determine its success or failure; the return value is actually
  a record to allow returning other data, such as the programs output
- make it very easy to override the input and output streams
- by default, pass no input to the program (instead of passing
  anything the user might type, as `Exec` does); this can be
  changed by adding an input stream to the argument list
- by default, capture any output of the program into a string
  and return it, instead of just printing the output to the
  console; this can be changed by adding an output stream to
  the argument list
Comment thread lib/helpview.gi
Exec(Concatenation("explorer.exe \"$(wslpath -a -w \"",file, "\")\""));

str := "";
Exec2("wslpath", "-a", "-w", url, OutputTextString(str, false));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you're using the variable url as the -w argument, but the replaced code was using the variable file. Is this a mistake or a deliberate change?

Comment thread lib/process.gi

end );

# TODO: document this, come up with a better name, write some tests...
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still a big TODO 🙂 I don't currently have a better name suggestion but I'll have a think.

Comment thread lib/process.gi
Error("must specify at most one working directory");
fi;
dir := a;
elif IsInputStream(a) then
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds reasonable to me at least @fingolfin.

@fingolfin fingolfin modified the milestones: GAP 4.15.0, GAP 4.16.0 Aug 28, 2025
@kiryph
Copy link
Copy Markdown

kiryph commented Jan 12, 2026

Naming possibilities based on other software:

  • System in Vim, Python lib os.system, Gnuplot
  • Run in Mathematica, Pyhon subprocess.run
  • RunProcess in Mathematica
  • Call in Python subprocess.call
  • Spawn in Node.js

Affected interfaces:

  • CaratInterface
  • Polymaking
gap> # some polymaking gives you always
polymake: upgrading /private/var/folders/2z/bljkcy1j2cbf7l9zf3dk0nqc0000gn/T/gaptempdiroer0l3/poly4822 from old plain file format
polymake: used package ppl
  The Parma Polyhedra Library ([[wiki:external_software#PPL]]): A C++ library for convex polyhedra
  and other numerical abstractions.
  http://www.cs.unipr.it/ppl/

IMHO this kind of info should only be shown on debug level or higher info level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind: enhancement Label for issues suggesting enhancements; and for pull requests implementing enhancements topic: library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants