-
-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Way to execute from a different directory? #112
Comments
I think |
Well I'm looking to execute from a different directory without actually changing my current directory, especially as os.chdir is going cause problems if I do any sort of parallelism/threading. |
Yes, I see your point. The |
A backwards compatible approach would be using variadic arguments to allow passing in optional parameters. This is a pretty common approach and is often used with structs or functions. Unfortunately I don't know if there is a name for that pattern. I prefer the approach using functions. type ExecOption func(&exec.Cmd)
func ExecWithDir(dir string) ExecOption {
return func(c &exec.Cmd) {
c.Dir = dir
}
}
func (p *Pipe) Exec(command string, opts ...ExecOption) *Pipe {
return p.Filter(func(r io.Reader, w io.Writer) error {
args, ok := shell.Split(command) // strings.Fields doesn't handle quotes
if !ok {
return fmt.Errorf("unbalanced quotes or backslashes in [%s]", command)
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = r
cmd.Stdout = w
cmd.Stderr = w
for _, o := range opts {
o(cmd)
}
err := cmd.Start()
if err != nil {
fmt.Fprintln(w, err)
return err
}
return cmd.Wait()
})
}
func main() {
Exec("ls", ExecWithDir("/root")).Stdout()
} |
Your example is the functional options API pattern. It's what I use too. It's very good. |
@ghostsquad TIL, thank you! The Blogpost about the talk that might have given it that name: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis |
That's the one! A really fantastic post. I love the flexibility it provides, and enables you to add functionality to an API without changing the function signatures. |
Functional options is definitely a possibility here, though it ends up being a little awkward because the option names need to be prefixed with the package name. A more realistic version of the example given is: script.Exec("/bin/ls", script.ExecWithDir("/some/directory/path")).Stdout() ...which tends to obscure the otherwise fluent pipeline syntax. You can imagine that if there were more options, this would soon no longer be a one-liner. |
That's true. But if you have complex pipelines like that, you could also write them in multiple lines, e.g. script.Exec("/bin/ls", script.ExecWithDir("/some/directory/path")).
Match("something").
Stdout() which is still very readable. |
The flow-breaking is still there, even if you spread it across multiple lines. And there's a question about what happens if you pipe the output of this I think if we were going to set the working directory for a pipe, which I must say I'm not yet convinced is necessary, I think it would be more useful to have this be an option for the pipe as a whole: script.NewPipe().WithWorkingDirectory("/home/me").Exec(...) |
If other methods like |
Or even just: script.NewPipe().At("/home/me")... |
I'm trying to figure out why we can't just run os.Chdir("home/me")
script.Exec("ls /tmp").Stdout() |
Because os.chdir is global. Not thread safe. It's not specific to the command. |
You paid too much attention to the os.chdir. It was just to show that no matter what’s the path you are you can pass via args to the cmd itself instead of setting it on the cwd field of exec without changing anything on the package |
I'm confused by this statement. You just said:
My answer is because workdir := "/foo/bar")
cmd := "ls"
script.Exec(fmt.Sprintf("cd %q && cmd", workdir, cmd)).Stdout() though I agree this is pretty common in shell scripts, e.g. (
cd "${workdir}";
ls
) It just feels really hacky, and there's not a whole lot of room for |
my idea is even simpler workdir := "/foo/bar"
cmd := "ls"
script.Exec(fmt.Sprintf("cmd workdir", workdir, cmd)).Stdout() |
What to do in the case that the command doesn't accept a path. |
It's a good suggestion, but I don't think I've seen enough widespread support to convince me that the lack of such a mechanism is a serious problem for a large number of users. Let's close for now and return to this if the issue is raised again by others. |
Just coming back to this, and I realized this has been asked for in various ways in several other issues, all of which dismissed in some form. Just to reiterate, using I'm just looking for something equivalent to setting the |
By all means make a specific proposal! What about this, for example: script.At("/home/john").Exec("ls") |
I do like that. |
Let's see if we can come up with one or two more realistic examples of programs using |
(
cd "./project"
make
) |
What would really help us here is a program that:
|
https://gobyexample.com/goroutines Replace fmt.Println with running a shell command. |
I'm not writing a program here to prove this feature is useful. It seems you don't believe that this feature is useful. I'll just close this and I'll go somewhere else. Thank you for your time. |
It's not a matter of belief: we simply don't need features that aren't needed! In other words, software exists not to provide employment for software engineers, but to solve user problems. If no one has a problem that this feature solves, it's better not to write it. Useless API methods serve only to clutter the program and make it harder to use. You have several times advanced the idea that there is a real use case requiring this feature, but despite many invitations, you've refused to say what it is. If you or anyone else can provide one, we'll implement this feature like a shot! |
I agree that features that no one will use should not exist, it's a waste of time. I have not yet met someone who openly admits to posting issues on GitHub for shits and giggles.
4 months ago I posted this:
I have a use case for this feature. Please let me know if I've been unclear in any way. |
Yes, I think I see where the confusion is coming from. "Use case" means different things to different people, but in this case I mean a real-life problem that that can only be solved if this feature is present. For example, maybe you want to build several projects simultaneously, so you need to concurrently run for _, p := range projects {
go script.At(p).Exec("make").Stdout()
} That's the kind of program I was asking for. Sorry that I was unclear—I don't know the right words to use to ask people to supply these examples. |
Is there a way I could have reworded this statement to improve clarity?
|
Yes, I think so. What you've given there is a solution, not a problem. In other words, you're framing your request in terms of "I want Feature X". That's totally legit, but it doesn't help the maintainers understand why you're asking for Feature X. The reason for asking why is not gatekeeping, or demanding that you justify the need for Feature X. It's to help us understand what the underlying problem is that you're trying to solve, to which you think (I'm sure correctly) that Feature X is the solution. And the reason we need to understand that is so that we can design the feature in such a way that it's best suited to the real problem you have, not some imaginary problem that we might come up with. What would a description of that problem look like? Something like the example I gave: "I want to build several projects simultaneously, by concurrently running That may not be the problem you have (I'm exercising my imagination, which is what we're saying maintainers shouldn't do), but it is at least a possible answer to the question "What kind of real-world problem would Feature X solve?" |
That wasn't a solution at all. Someone else proposed os.chdir, which I said would not work because it's not threadsafe. Thus, a threadsafe mechanism to manage the current working directory is in fact the problem that I want to solve. |
I mention threadsafety a second time here:
And a third time in this comment:
|
You paid too much attention to the os.chdir. It was just to show that no
matter what’s the path you are you can pass via args to the cmd itself
instead of setting it on the cwd field of exec without changing anything on
the package
On Fri, 13 May 2022 at 23:01 Wes McNamee ***@***.***> wrote:
Because os.chdir is global. Not thread safe. It's not specific to the
command.
—
Reply to this email directly, view it on GitHub
<#112 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AATHKBJS6ENTNKB4TEI7OKTVJ4CPJANCNFSM5T2QSA3Q>
.
You are receiving this because you commented.Message ID:
***@***.***>
--
[]s
Thiago Nache
|
I have a real-world example where I need to create a temporary directory, git clone a repo into that directory, then do some "work" (the work could be updating project dependencies, running static analysis, executing code modification, etc). I can create the temporary directory before invoking Is that helpful? |
I guess in my case I can just use something like: But then certain tools executed via |
Thanks @wburningham for pointing out that
Here's the problem: I cannot execute a command while controlling the working directory in a threadsafe way. |
FWIW, I like the |
Piling on here a little: In general, I'd like to
I'm a pretty new To the maintainer: thanks, |
I use this package heavily in my automation script(s). There are cases where this feature would be useful. For example, I pull a few repositories in parallel and execute commands in the cloned directories. While I can use |
@bitfield Looks like there quite a few requests for this feature. Can I pick it up? |
Certainly, and here's where we've been stuck up to now:
|
@krolikbrunatny I'm trying to put together a proposal for this feature. You mentioned that you use this package from an automation script. Would you be able to show me an example of how you're working around not having this feature with |
How would you choose the directory the
script.Exec
is running in?The text was updated successfully, but these errors were encountered: