goexecgo-ethereumgeth

Command that works in terminal doesn't work with go exec.Command


I'm trying to attach to a go-ethereum node from a go script:

accountInfo:= fmt.Sprintf("attach %v --exec 'eth.getBalance(eth.accounts[0])'", connect)
    //x, err := exec.Command("geth", accountInfo).Output() // returns 'Fatal: Unable to attach to remote geth: no known transport for URL scheme "c"'
    x, err := exec.Command("geth", accountInfo).Output() // returns "invalid command: "attach ws://127.0.0.1:8101...." <--- rest of the metaData string

    if err != nil {
        fmt.Println(err)
    }

This command works perfectly fine in terminal but it keeps telling me it's invalid when running it like this. This is making me go nuts.


Solution

  • From the os/exec documentation:

    Unlike the "system" library call from C and other languages, the os/exec package intentionally does not invoke the system shell and does not expand any glob patterns or handle other expansions, pipelines, or redirections typically done by shells.

    Since the arg ...string parameter of exec.Command() isn't processed by a shell, each argument is handed to the command exactly as specified. In your case, the entire content of metaData is provided to geth as a single argument.

    You should instead create a slice of strings, each containing a single argument. And then provide that slice as the arg parameter using the ... notation.

    Here's an example demonstrating this, using the uname command:

    package main
    
    import (
        "fmt"
        "os/exec"
    )
    
    func main() {
        command := "uname"
        argsString := "--kernel-name --machine"
        argsSlice := []string{"--kernel-name", "--machine"}
    
        // Equivalent command:
        // $ uname "--kernel-name --machine"
        fmt.Println("exec.Command(command, argsString)")
        stringOutput, err := exec.Command(command, argsString).CombinedOutput()
        if err != nil {
            fmt.Printf("Failed: %s\n", err)
        }
        fmt.Printf("Output:\n%s\n", stringOutput)
    
        // Equivalent command:
        // $ uname --kernel-name --machine
        fmt.Println("exec.Command(command, argsSlice)")
        sliceOutput, err := exec.Command(command, argsSlice...).CombinedOutput()
        if err != nil {
            fmt.Printf("Failed: %s", err)
        }
        fmt.Printf("Output:\n%s\n", sliceOutput)
    }
    

    And it's otuput:

    $ go run main.go
    exec.Command(command, argsString)
    Failed: exit status 1
    Output:
    uname: unrecognized option '--kernel-name --machine'
    Try 'uname --help' for more information.
    
    exec.Command(command, argsSlice)
    Output:
    Linux x86_64