Bash getopts command in Golang - go

How do I pass a list as a flag variable in Golang? For example in Bash you can pass a list as getopts to the script. Something like that:
./myScript.sh -n list_of_names.txt //some file with about 50 names
and later loop over the list:
#!/bin/bash
while getopts "n:h:" options; do
case "${options}" in
"n") NAME=${OPTARG};;
"h") PRINT_USAGE;;
esac
done
for i in $NAME; do
//TODO
I've heard about the "flag" package, but I have no idea how to achieve that
PS I'm a complete newbie to Go

In your example, you really only have one required argument, so you don't really need to parse anything. You can just check the slice length:
package main
import "os"
func main() {
if len(os.Args) != 2 {
println("myScript <file>")
os.Exit(1)
}
name := os.Args[1]
println(name)
}
https://golang.org/pkg/os#Args

Related

How to directly invoke the system shell in Go (golang)?

As per the golang documentation, go does not make a call to the system's shell when you are using exec.Command().
From the golang.org documentation on the "os/exec" package:
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.
This presents a problem. Because of this design choice you cannot use piping when executing a command. Therefore the following code does not execute as desired.
package main
import (
"fmt"
"os/exec"
)
func main() {
exec.Command("echo", "Hello", ">>", "~/thing").Run()
cmdOut, _ := exec.Command("cat", "~/thing").Output()
fmt.Println(cmdOut)
}
Instead of printing out the contents of a file that should contain the word 'Hello,' it instead prints out a blank newline. I have tried directly invoking bash like this:
package main
import (
"fmt"
"os/exec"
)
func main() {
exec.Command("bash", "-c", "echo", "Hello", ">>", "~/thing").Run()
cmdOut, _ := exec.Command("cat", "~/thing").Output()
fmt.Println(cmdOut)
}
This, however, produces the same result as the original code. How can I directly invoke the system shell when using golang?
The second argument should be one string. In shell command you need to pass it as one string too. Also ~ is interpreted by bash. You can safely assume that sh exists. Bash shell is not a must.
package main
import (
"fmt"
"os/exec"
)
func main() {
exec.Command("sh", "-c", "echo Hello >> ~/thing").Run()
cmdOut, _ := exec.Command("sh", "-c", "cat ~/thing").Output()
fmt.Println(cmdOut)
}

Running a command from a string with parameters in go

I am trying to run a command with go. The command is in a string.
package main
import (
"log"
"os"
"os/exec"
"strings"
"github.com/davecgh/go-spew/spew"
)
func main() {
commandToRun := `echo $HOME`
log.Printf("Running %s\n", commandToRun)
args := strings.Fields(commandToRun)
spew.Dump(args[1:len(args)])
command := exec.Command(args[0], args[1:len(args)]...)
command.Stdout = os.Stdout
command.Stdin = os.Stdin
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Printf("Command finished with error: %v", err)
}
}
The output is:
2018/11/14 09:41:22 Running echo $HOME
([]string) (len=1 cap=1) {
(string) (len=5) "$HOME"
}
$HOME
What I'd like to have is:
2018/11/14 09:41:22 Running echo $HOME
([]string) (len=1 cap=1) {
(string) (len=5) "$HOME"
}
/home/whatever
Looks like go is sanitizing the string somehow. So the $HOME is not expanded. Is there any way of running the string exactly as if it was typed into the shell?
This is the important part. Ideally I'd like to turn from string to type in the current shell.
EDIT: The example below solve the simplest scenario but doesn't cover the "running the string exactly as if it was typed into the shell" part.
If I switch to expandenv:
commandToRun := os.ExpandEnv(`echo "$HOME"`)
I get:
2018/11/14 11:45:44 Running echo "/Users/rafael"
([]string) (len=1 cap=1) {
(string) (len=15) "\"/home/whatever\""
}
"/home/whatever"
What I'd get in the shell is:
$ > echo "$HOME"
/home/whatever
without the quotes.
This is close to what I want but not exactly it.
$HOME (and all other env variables) are expanded by the shell. You're not executing a shell, so they don't get expanded.
You need to look up the env variable directly in go, with something like:
command := exec.Command("echo", os.Getenv("HOME"))
or this:
commandToRun := os.ExpandEnv("echo $HOME")
args := strings.Fields(commandToRun)
command := exec.Command(args[0], args[1:]...)
Note that this last approach won't work if $HOME expands to a string containing whitespace, so the os.Getenv method is generally safer/preferred for this use case.
Before executing the command, you can actively expand all env vars in the string using os.ExpandEnv:
os.ExpandEnv("echo $HOME")
From the docs:
ExpandEnv replaces ${var} or $var in the string according to the values of the current environment variables. References to undefined variables are replaced by the empty string.
If you want to get the output of $ echo $HOME, the minimal code you need is
fmt.Println(os.Getenv("HOME"))
Nothing more is needed.
If you use os.ExpandEnv("echo $HOME"), then first $HOME var will be expanded and then it will give you a string like echo /home/<user>
If you use command := exec.Command("echo", os.Getenv("HOME")), then it will be resolved as command := exec.Command("echo", "/home/<user>") and finally which will give output /home/<user>
If you use
commandToRun := os.ExpandEnv("echo $HOME")
command := exec.Command(strings.Fields(commandToRun)...)
then it will be process like previous cases.
So better way is using only fmt.Println(os.Getenv("HOME")).

Using Go to spawn a shell with a TTY from "nc -e /bin/bash"

I want to escape a restricted shell spawning a bash shell via Go. In other words, I want to do this but using Go:
python -c 'import pty; pty.spawn("/bin/bash")'
I am totally new to Go. I have tried this (following the answer in this question Go: How to spawn a bash shell) but nothing happens:
package main
import "os"
import "os/exec"
func main() {
shell := exec.Command("/bin/bash")
shell.Stdout = os.Stdout
shell.Stdin = os.Stdin
shell.Stderr = os.Stderr
shell.Run()
}
Also if I add the fmt.Println("hello") line at the end of the main function nothing is printed
UPDATE
Maybe I did not expalined well. What I am trying to achieve it's to spawn a shell gotten a restricted shell. This is what I did:
Listener:
nc.traditional -l -p 8080 -e /bin/bash
Connects to listener: And I exec the code here
nc.traditional localhost 8080 -v
Your program works fine for me. I put some error checking in and an extra print statement which should make what is happening clearer. You are getting an interactive shell, it just looks exactly like your previous shell.
$ go run Go/shell.go
$ # whoa another shell
$ exit
exit
exiting
$ # back again
$
Here is the revised program
package main
import (
"fmt"
"log"
"os"
"os/exec"
)
func main() {
shell := exec.Command("/bin/bash")
shell.Stdout = os.Stdout
shell.Stdin = os.Stdin
shell.Stderr = os.Stderr
err := shell.Run()
if err != nil {
log.Fatalf("command failed: %v", err)
}
fmt.Printf("exiting\n")
}

Persist the value set for an env variable on the shell after the go program exits

Is there a way i can set an Environment variable on my shell and have it persist after the go program exits ? I tried the following
bash-3.2$ export WHAT=am
bash-3.2$ echo $WHAT
am
bash-3.2$ go build tt.go
bash-3.2$ ./tt
am
is your name
bash-3.2$ echo $WHAT
am
bash-3.2$
The code was :
package main`
import (
"fmt"
"os"`
)
func main() {
fmt.Println(os.Getenv("WHAT"))
os.Setenv("WHAT", "is your name")
fmt.Println(os.Getenv("WHAT"))
}
Thanks
No, environment variables can only be passed down, not up. You're trying to do the latter.
Your process tree:
`--- shell
`--- go program
|
`--- other program
The go program would have to pass the environment variable up to the shell so that the other program can access it.
What you can do is what programs like ssh-agent do: return a string that can be interpreted as setting a environment variable which can then be evaluated by the shell.
For example:
func main() {
fmt.Println("WHAT='is your name'")
}
Running it will give you:
$ ./goprogram
WHAT='is your name'
Evaluating the printed string will give you the desired effect:
$ eval `./goprogram`
$ echo $WHAT
is your name
No.
A process has a copy of its parent's environment and can't write to the parent environment.
The other answers are strictly correct, however you are free to execute your golang code to populate arbitrary values to environment variables into an output file your go creates then back in the parent environment from which you executed that go binary then source the go's output file to have available env variables calculated from inside your go code ... this could be your go code write_to_file.go
package main
import (
"io/ioutil"
)
func main() {
d1 := []byte("export whodunit=calculated_in_golang\n")
if err := ioutil.WriteFile("/tmp/cool_file", d1, 0644); err != nil {
panic(err)
}
}
now compile above write_to_file.go into binary write_to_file ... here is a bash script which can act as the parent to execute above binary
#!/bin/bash
whodunit=aaa
if [[ -z $whodunit ]]; then
echo variable whodunit has no value
else
echo variable whodunit has value $whodunit
fi
./write_to_file # <-- execute golang binary here which populates an exported var in output file /tmp/cool_file
curr_cool=/tmp/cool_file
if [[ -f $curr_cool ]]; then # if file exists
source /tmp/cool_file # shell distinguishes sourcing shell from executing, sourcing does not cut a subshell it happens in parent env
fi
if [[ -z $whodunit ]]; then
echo variable whodunit still has no value
else
echo variable whodunit finally has value $whodunit
fi
here is the output from executing above shell script
variable whodunit has value aaa
variable whodunit finally has value calculated_in_golang

Call source from inside a Go program

For fun and to better learn Go, I'm trying to re-implement antigen in Go.
Problem is: source is a shell built-in function, so I can't call it with os/exec Command function, because it expects an executable in PATH.
How can I do this? And, is it possible to make a source from inside a go program affect the user shell?
You can write the command directly in the terminal device. But, to do that, first you need to know which device is using the user. A script that executes your program can be a solution.
#!/bin/bash
echo Running from foo script, pid = $$
go run foo.go `tty`
Then, the program has to write the commands to the terminal device.
package main
import (
"C"
"fmt"
"os"
"syscall"
"unsafe"
)
func main() {
// Get tty path
if len(os.Args) < 2 {
fmt.Printf("no tty path\n")
os.Exit(1)
}
ttyPath := os.Args[1]
// Open tty
tty, err := os.Open(ttyPath)
if err != nil {
fmt.Printf("error opening tty: %s\n", err.Error())
os.Exit(2)
}
defer tty.Close()
// Write a command
cmd := "echo Hello from go, pid = $$\n"
cmdstr := C.CString(cmd)
cmdaddr := uintptr(unsafe.Pointer(cmdstr))
for i := range []byte(cmd) {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCSTI, cmdaddr+uintptr(i))
if uintptr(err) != 0 {
fmt.Printf("syscall error: %s\n", err.Error())
os.Exit(3)
}
}
}
Here is an example output:
$ echo $$
70318
$ ./foo
Running from foo script, pid = 83035
echo Hello from go, pid = $$
$ echo Hello from go, pid = $$
Hello from go, pid = 70318
Note that I am executing the script with ./ not source, so the PID of the script differs. But later, the command executed by the go program has the same PID.

Resources