The ability to execute external commands from within an application is something I often feel is a bit hackish and I haven’t yet discovered a language that handles it as well as I would like. That was until I learned to love how Go tackles the challenge. This post will show you how to make the most of
I tend to feel that there are three different types of command execution within applications.
- Plain output. For when you always expect the command to execute but all you want from it is the output.
- Exit codes. These perform some cleanup, setup or check and you want to discard any output but assert the exit code.
- Long running processes. This case is rarer but there are times when I want to spawn sub-processes. Maybe even starting up another server to proxy to for instance.
So how do I handle these in Go? The magic all comes from the wonderful os/exec which is part of the Go standard library. Using this library, commands become a first class citizen within your application.
For all examples I am only going to include the primary logic but I will keep a link to a full copy in the Go Playgroud. The copies won’t execute within the Playground due to the restrictions Google have in place but they should run fine locally.
I have defined the following functions to keep the examples short:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
The first and most obvious use is to collect output from an external command. An easy way to do that is to use the
1 2 3 4 5 6 7 8 9 10
This works well if you also want to check for any error messages output but if you want finer control over the output of a command then we can route it into different buffers giving us control over both standard output and standard error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
In the above example we manually connect our own buffer to capture the commands stdout stream. We can do the same for stderr and even stdin so long as it adheres to the
So far we’ve seen how easy it is to capture command output across multiple file descriptors. It’s more verbose then other languages but it gives us lots of flexibility.
Retrieving the exit code of a command is easy with Go. You may have already noticed in the previous examples that when executing the command Go can return an error. These errors occur if there is an issue with IO or of the command doesn’t return a successful exit code (0).
The following code will output the exit code of a command.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
The above code looks more complicated but in traditional Go fashion it handles many situations.
- Create an
- Execute and test for any errors
- If we received an error then check it was because of the commands exit
- If no error then check the commands
*os.ProcessStatefor the exit code (pretty much guaranteed to be 0 but in here for completion)
A caveat to take note of is that if Go failed to locate the command in your
$PATH then it won’t ever execute the command and thus you will have no exit code. This is why it is important to assert the type of error returned.
Long running processes
All our above examples are synchronous. They wait for the command to complete before continuing the execution of our application. If we wanted to execute a long running task however it is likely that we want it to happen asynchronously. Once again this is trivially easy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
Now that was a lot more work but it all makes good sense. I started a computationaly difficult task of generating a collection of random bytes. Next I start that command asynchronously and then begin a ticker to show the elapsed time and a timer to kill the process after 4 seconds. Once the process has been killed then we output the total number of generated bytes.
A small disclaimer. I don’t claim that this is the best way to do this but it demonstrates asynchronous commands and the ability to send signals to the process within our application.
This has been quite a technical post but we have covered lots of ground with the flexibility of
os/exec. I have tested all the examples on my Mac using go1.3. Any suggestions/improvements are welcomed.
I hope I’ve been able to get across why I’m beginning to really enjoy working with Go.