Getting error back after executing a shell script via Golang

1,123 views
Skip to first unread message

DM

unread,
Sep 29, 2016, 4:47:36 AM9/29/16
to golang-nuts
Cross-posting this from stackoverflow:-

I have a simple shell script (named copy.sh) which looks like below:-
#! /bin/sh

cp $1 $2

I did chmod 777 copy.sh.

I have a golang code which executes the above shell code:-

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    _, err := exec.Command("/Users/debraj/copy.sh", "/Users/debraj/temp.txt", "/Users/debraj/gotest/").Output()
    if err != nil {
        fmt.Println("Failed to execute command " + err.Error())
        return
    }
    fmt.Printf("\nCopy Successful - %v")
}

The above code is showing be the below output:-

jabongs-MacBook-Pro-4:src debraj$ go run copyerr.go 
Failed to execute command exit status 1
jabongs-MacBook-Pro-4:src debraj$ 

But the error I receive from shell script looks like below:-

jabongs-MacBook-Pro-4:~ debraj$ ./copy.sh /Users/debraj/temp.txt /Users/debraj/gotest/
cp: /Users/debraj/gotest/temp.txt: Permission denied     

Can someone let me know how how can I get the same error message that is returned by the shell script?

If I don;t do chmod 777 copy.sh and the file has permission as below:-

jabongs-MacBook-Pro-4:~ debraj$ ls -la copy.sh 
-rw-r--r--  1 debraj  staff  21 Sep 29 13:28 copy.sh

Then the golang code gives the output as given by the shell script. Can some also let me know why this is behaving like this?

I am on

  • Golang 1.7
  • Mac OS X 10.11.4

Konstantin Khomoutov

unread,
Sep 29, 2016, 6:23:01 AM9/29/16
to DM, golang-nuts
On Thu, 29 Sep 2016 01:47:36 -0700 (PDT)
DM <subhara...@gmail.com> wrote:

> Cross-posting this from stackoverflow
> <http://stackoverflow.com/questions/39764600/getting-error-back-after-executing-a-shell-script-via-golang>
> :-
>
> I have a simple shell script (named copy.sh) which looks like below:-
>
> #! /bin/sh
>
> cp $1 $2
>
> I did chmod 777 copy.sh.

Never to this: you've just granted full write access to everyone (the
last 7). Executables should have 755 or 775 and no more, and that's if
you don't care about everyone reading their contents.

> I have a golang code which executes the above shell code:-
>
> package main
> import (
> "fmt"
> "os/exec")
>
> func main() {
> _, err := exec.Command("/Users/debraj/copy.sh",
> "/Users/debraj/temp.txt", "/Users/debraj/gotest/").Output() if err !=
> nil { fmt.Println("Failed to execute command " + err.Error())
> return
> }
> fmt.Printf("\nCopy Successful - %v")}
>
> The above code is showing be the below output:-
>
> jabongs-MacBook-Pro-4:src debraj$ go run copyerr.go Failed to execute
> command exit status 1 jabongs-MacBook-Pro-4:src debraj$
>
> But the error I receive from shell script looks like below:-
>
> jabongs-MacBook-Pro-4:~ debraj
> $ ./copy.sh /Users/debraj/temp.txt /Users/debraj/gotest/
> cp: /Users/debraj/gotest/temp.txt: Permission denied
>
> Can someone let me know how how can I get the same error message that
> is returned by the shell script?
[...]

That's simple: you're confusing exit codes of OS processes [1] with
messages they write to their standard output streams [2]. That
"cp: /Users/debraj/gotest/temp.txt: Permission denied" is a _message_
the `cp` program run by your script writes to its standard error stream.
It then exits with some non-zero exit status indicating failure to do
its job. Since that's the last command run by the script, that exit
status also becomes the exit status of the whole script.

Now the Go runtime machinery which was executing your script waited for
its process to terminate, collected its exit code and since it was not
zero (indicating success), it generated and returned a non-nil error
value. That error value contained the exit code.

Now note that Go errors must implement the standard interface which has
the name "error" and is defined to be

type error interface {
func Error() string
}

and, Error() is what you had called on that error value to obtain its
_string representation_ -- basically what the error value's concrete
type thinks its values should look like when rendered as text.

As you can see, what Error() returns for the error value generated by
exec.Command() has no bearing on what the process it were executing
output to its standard streams (or elsewhere). It only "knows" about
that process' exit status (and may be some other platform-specific
information).

So, if you want to "grab" what your spawned process had written to its
standard streams (or only the error stream) before dying, you need to
explicitly capture this information.

Now see what you did:

1) A call to os/exec.Command() returned a pointer to a value of type
os/exec.Cmd it constructed.
2) You then immediately called Output() on that pointer.

So let's see the documentation on the latter:

| $ go doc os/exec.Cmd.Output
| func (c *Cmd) Output() ([]byte, error)
| Output runs the command and returns its standard output. Any
| returned error will usually be of type *ExitError. If c.Stderr was
| nil, Output populates ExitError.Stderr.

So, this function only returns what the process had written to its
standard output stream -- not its standard error stream (where error
messages usually go). But it also appears to collect what the process
writes to its standard error stream if it has not been redirected.

OK, by now you should be inspecting the docs on os/exec.Command,
os/exec.Cmd and figuring out that the former indeed constructs a value
of os/exec.Cmd with its Stderr property left with its default value,
nil, which means os/exec.Cmd.Output() indeed should have collected the
error stream of your process in the error's value Stderr property.

Now please also study the documentation on os/exec.ExitError.
There, you can learn that os/exec.Cmd.Output() is smart about
collecting the error stream so that it wont grab overlong error outputs
of the process.

If it's OK for you, you should roll like this:

_, err := exec.Command(...).Output()
if err != nil {
ee, ok := err.(*exec.ExitError)
if !ok {
return err
}
// OK, so we really have an instance of ExitError
// Now do something with the value of ee.Stderr
// which is of type []byte.
}

You may of course consider other approaches as os/exec allows you to do
any sort of things with handling the output streams of the process
being executed.

To help you further, we'll need more information on what you really
want to do.


A side note: the shell command language works much like PHP: if any
executed command returns an error the script interpreted continues
to chug away. If you need to fail as soon as possible always start
your scripts with the

set -e -u

primitive (this also should be done for scripts executed in subshells).

1. https://en.wikipedia.org/wiki/Exit_status
2. https://en.wikipedia.org/wiki/Standard_streams
Reply all
Reply to author
Forward
0 new messages