I use the following code to create command which should run according to some flags that are
passed from the cli.
I use the cobra repo
https://github.com/spf13/cobra
when I run it with go run main.go echo test
I get
Print: test
which works.
Now I run go install open the
bin directory and click on the file newApp (this my name of my app)
and it prints
Usage:
MZR [command]
Available Commands:
echo Echo anything to the screen
help Help about any command
print Print anything to the screen
Flags:
-h, --help help for MZR
Use "MZR [command] --help" for more information about a command.
[Process completed]
And I cannot use any commands (like MZR echo) which I was able when I run it locally with go run main.go echo test
But I want to use it like following MZR -h or MZR echo ,
How I can do it ?
(and also give to my friend the file from the bin that created after go install - which is Unix executable - 3.8 MB )
e.g. like this repo which use the same command line tools and to run it you use hoarder --server
https://github.com/nanopack/hoarder
This is the code for example (to make it more simpler )
package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
func main() {
var echoTimes int
var cmdPrint = &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdTimes = &cobra.Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
for i := 0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
var rootCmd = &cobra.Command{Use: "MZR"}
rootCmd.AddCommand(cmdPrint, cmdEcho)
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
The name of the executable is taken from the directory name. Rename the directory newApp to MZR. With this change, the go install command will create a executable with the name MZR. If the executable is on your path, then you can run it from the command line using MZR -h or MZR echo,
Related
Golang's flag package reads the command line flags and args properly if the input provided is of the form : go run main.go -o filename.txt arg1 arg2
But if I try to provide the input like : go run main.go arg1 arg2 -o filename.txt, everything after main.go is read as arguments.
How to make this style work?
My program:
package main
import (
"flag"
"fmt"
)
func main() {
var output string
flag.StringVar(&output, "o", "", "Writes output to the file specified")
flag.Parse()
fmt.Println("Positional Args : ", flag.Args())
fmt.Println("Flag -o : ", output)
}
go run main.go -o filename.txt arg1 arg2
Output:
Positional Args : [arg1 arg2]
Flag -o : filename.txt
go run main.go arg1 arg2 -o filename.txt
Output:
Positional Args : [arg1 arg2 -o filename.txt]
Flag -o :
If you shimmy around with the contents of os.Args, it is possible to accept arg1 arg2 -o filename.txt
Go through the os.Args that is passed in from the command line in the for loop
If a - is seen then set a condition that indicates the first flag has been seen
If the condition is set then populate the "notargs" list. Otherwise, populate the "args" list
There is a bit of extra complication here as the args list that is used to set os.Args to the values that will do the flag processing must include the program name (the original os.Arg[0]) as the first value
This solution does not work with -o filename.txt arg1 arg2
package main
import (
"flag"
"fmt"
"os"
)
func main() {
var output string
var args[]string
var notargs[]string
var in_flags bool=false
for i:=0; i<len(os.Args); i++ {
if os.Args[i][0]=='-' {
in_flags=true
}
if i==0 || in_flags {
notargs=append(notargs,os.Args[i])
} else {
args=append(args,os.Args[i])
}
}
os.Args=notargs
flag.StringVar(&output, "o", "", "Writes output to the file specified")
flag.Parse()
fmt.Println("args ",args)
fmt.Println("Flag -o : ", output)
}
I wrote a bash script to automate download my Go program and install to my linux machine. I can via the curl to download but when my Go program prompt the user input, it go to infinite loop. The error shows EOF error.
Do anyone have any idea about it?
Install.sh
#!/bin/bash
set -e
set -o noglob
curl https://coderkk.net/ReadInput -o ReadInput
chmod a+x ReadInput
./ReadInput
ReadInput.go
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
var text string
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("Please enter you name here : ")
text, _ = reader.ReadString('\n')
text = strings.Replace(text, "\n", "", -1)
if text != "" {
break
}
}
fmt.Printf("Hi %s\n", text)
}
The problem is not related to Go - although you shouldn't ignore any potential errors from reader.ReadString - it is due to how you are invoking your downloaded script.
The pipe takes the output of curl and turns that into the stdin for sh. This is fine but your script requires stdin from the terminal - which is now lost.
Change your invocation method to something like this:
sh -c "$(curl -sfL https://coderkk.net/install.sh)"
this will pass the contents of your script to the sh interpreter but preserve the user's terminal stdin.
I have a simple Go web server:
package main
import (
"net/http"
"strings"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
message := r.URL.Path
message = strings.TrimPrefix(message, "/")
message = "Hello " + message
w.Write([]byte(message))
}
func main() {
http.HandleFunc("/", sayHello)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
And a Makefile that runs it and caches the PID to a PID file:
GOSRC=$(wildcard *.go)
.PHONY: kill
kill:
echo ""
.PHONY: neaten
neaten:
go clean
go mod tidy
go mod download
goimports -w $(GOSRC)
go vet $(GOSRC)
go test -cover $(GOSRC)
.PHONY: build
build: neaten
go build -o server $(GOSRC)
.PHONY: run
run: kill build
nohup ./server &
echo $! > server.pid
However, echo $! does not work for some reason, what I get is an empty server.pid file.
The process is definitely running since I can access the web server. But echo $! doesn't do anything.
I have also tried echo $$ but that also doesn't do much.
I'm also running the Makefile from the fish terminal.
When you run command from make, each command is executed in a separate shell:
run: kill build
nohup ./server &
echo $! > server.pid
The 'nohup' will execute in one shell, and echo will be executed in a different shell. As a result, the PID of the back grounded process ($!) is available in the first line, but NOT in the second.
The simple solution is to place the two command on the same line.
run: kill build
nohup ./server & echo $! > server.pid
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")).
Is it possible to pass a string of go code into go run instead of go run /some/path/script.go? I tried:
echo "some awesome go code here" | go run
But does not work. Thanks.
I don't think that there is such an option. At least not with the standard *g compilers or
go run.
You can try using gccgo as GCC supports reading from stdin.
Since I thought that this would be a useful thing to have, I wrote a relatively small Python script that achieves what I think you want. I called it go-script, and here are some usage examples:
# Assuming that test.go is a valid go file including package and imports
$ go-script --no-package < test.go
# Runs code from stdin, importing 'fmt' and wrapping it in a func main(){}
$ echo 'fmt.Println("test")' | go-script --import fmt --main
$ echo 'fmt.Println("test")' | go-script -ifmt -m
Help:
Usage: go-script [options]
Options:
-h, --help show this help message and exit
-i PACKAGE, --import=PACKAGE
Import package of given name
-p, --no-package Don't specify 'package main' (enabled by default)
-m, --main Wrap input in a func main() {} block
-d, --debug Print the generated Go code instead of running it.
The source (also available as a gist):
#!/usr/bin/env python
from __future__ import print_function
from optparse import OptionParser
import os
import sys
parser = OptionParser()
parser.add_option("-i", "--import", dest="imports", action="append", default=[],
help="Import package of given name", metavar="PACKAGE")
parser.add_option("-p", "--no-package", dest="package", action="store_false", default=True,
help="Don't specify 'package main' (enabled by default)")
parser.add_option("-m", "--main", dest="main", action="store_true", default=False,
help="Wrap input in a func main() {} block")
parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False,
help="Print the generated Go code instead of running it.")
(options, args) = parser.parse_args()
stdin = ""
for line in sys.stdin.readlines():
stdin += "%s\n" % line
out = ""
if options.package:
out += "package main\n\n"
for package in options.imports:
out += "import \"%s\"\n" % package
out += "\n"
if options.main:
out += "func main() {\n%s\n}\n" % stdin
else:
out += stdin
if options.debug:
print(out)
else:
tmpfile = "%s%s" % (os.environ["TMPDIR"], "script.go")
f = open(tmpfile, 'w')
print(out, file=f)
f.close()
os.execlp("go", "", "run", tmpfile)
This works
cat <<EOF | tee /tmp/blah.go | go run /tmp/blah.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
EOF
If you want to not have to open a file and edit it first. Although I wouldn't find this super practical for every day use.