So I have a Go program that reads from STDIN as such below. I want the username and password to be entered from keyboard or device but the string slice can be passed using pipe. If I run the command as below:
echo "Hello World" | go run main.go
os.Stdin will be set to read from pipes and never the keyboard. Is there a way that I can change os.Stdin FileMode as such that it will be reading from device, i.e. keyboard for username and password?
I tried using os.Stdin.Chmod(FileMode) but received this error:
chmod /dev/stdin: invalid argument
func main() {
var n = []string{}
scanner := bufio.NewScanner(os.Stdin)
fmt.Println("Please type anything with Newline Separated, empty line signals termination")
for scanner.Scan() {
h := scanner.Text()
if h == "" {
break
}
n = append(n, h)
}
if err := scanner.Err(); err != nil {
fmt.Printf("Error in reading from STDIN: %v\n", err)
}
reader := bufio.NewReader(os.Stdin)
os.Stdout.WriteString("Username: ")
username, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("Unable to read username: %v\n", err)
}
username = strings.TrimSpace(username)
os.Stdout.WriteString("Password: ")
bytePassword, _ := terminal.ReadPassword(int(os.Stdin.Fd()))
password := string(bytePassword)
os.Stdout.WriteString("\n")
}
Probably scanf could help, check this example:
https://play.golang.org/p/tteQNl0trJp
package main
import (
"fmt"
)
func main() {
fmt.Println("Enter your name")
var name string
fmt.Scanf("%s", &name)
fmt.Printf("name = %s\n", name)
}
Something a little more elaborated to check if there is something to read from stdin and if not prompt the user:
https://play.golang.org/p/7qeAQ5UNhdQ
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
// check if there is somethinig to read on STDIN
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
var stdin []byte
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
stdin = append(stdin, scanner.Bytes()...)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("stdin = %s\n", stdin)
} else {
fmt.Println("Enter your name")
var name string
fmt.Scanf("%s", &name)
fmt.Printf("name = %s\n", name)
}
}
You can instead read from /dev/tty as this is always the terminal (if the program runs on a terminal). This is portable only to Unix-like systems (Linux, BSD, macOS, etc) and won't work on Windows.
// +build !windows
tty, err := os.Open("/dev/tty")
if err != nil {
log.Fatalf("can't open /dev/tty: %s", err)
}
scanner := bufio.NewScanner(tty)
// as you were ...
Related
In Go, I would like to execute a binary from within my application and continually read what the command prints to stdout. However, the one caveat is that the binary is programmed to execute its task infinitely until it reads the enter key, and I don't have access to the binary's source code. If I execute the binary directly from a terminal, it behaves correctly. However, if I execute the binary from within my application, it somehow thinks that it reads the enter key, and closes almost immediately. Here is a code snippet demonstrating how I'm trying to execute the binary, pipe it's stdout, and print it to the screen:
func main() {
// The binary that I want to execute.
cmd := exec.Command("/usr/lib/demoApp")
// Pipe the command's output.
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Println(err)
}
stdoutReader := bufio.NewReader(stdout)
// Start the command.
err = cmd.Start()
if err != nil {
fmt.Println(err)
}
// Read and print the command's output.
buff := make([]byte, 1024)
var n int
for err == nil {
n, err = stdoutReader.Read(buff)
if n > 0 {
fmt.Printf(string(buff[0:n]))
}
}
_ = cmd.Wait()
}
Any ideas if what I'm trying to accomplish is possible?
As #mgagnon mentioned, your problem might lie somewhere else; like perhaps the external dependency just bails due to not running in a terminal. Using following to simulate demoApp:
func main() {
fmt.Println("Press enter to exit")
// Every second, report fake progress
go func() {
for {
fmt.Print("Doing stuff...\n")
time.Sleep(time.Second)
}
}()
for {
// Read single character and if enter, exit.
consoleReader := bufio.NewReaderSize(os.Stdin, 1)
input, _ := consoleReader.ReadByte()
// Enter = 10 | 13 (LF or CR)
if input == 10 || input == 13 {
fmt.Println("Exiting...")
os.Exit(0)
}
}
}
... this works fine for me:
func main() {
cmd := exec.Command("demoApp.exe")
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
// After 3 seconds of running, send newline to cause program to exit.
time.Sleep(time.Second * 3)
io.WriteString(stdin, "\n")
}()
cmd.Start()
// Scan and print command's stdout
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Wait for program to exit.
cmd.Wait()
}
$ go run main.go
Press enter to exit
Doing stuff...
Doing stuff...
Doing stuff...
Exiting...
The only difference between this and your code is that I'm using stdin to send a newline after 3 seconds to terminate the cmd. Also using scanner for brevity.
Using this as my /usr/lib/demoApp:
package main
import (
"fmt"
"time"
)
func main() {
for {
fmt.Print("North East South West")
time.Sleep(time.Second)
}
}
This program works as expected:
package main
import (
"os"
"os/exec"
)
func main() {
cmd := exec.Command("demoApp")
stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
}
cmd.Start()
defer cmd.Wait()
for {
var b [1024]byte
stdout.Read(b[:])
os.Stdout.Write(b[:])
}
}
I can not take input from the user in Golang by use fmt.scan().
package main
import "fmt"
func main() {
fmt.Print("Enter text: ")
var input string
e, _ := fmt.Scanln(&input)
fmt.Println(input)
fmt.Println(e)
}
image of code
After stopping the debugger:
image of code
The err added to code, but nothing happened.
func main() {
fmt.Print("Enter text: ")
var input string
e, err := fmt.Scanln(&input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(input)
fmt.Println(e)
}
Image after add err in my Code. What is "not available" in Next line (after my Input value: "51213")
You code has no problem. If you build your code with go build and run the binary directly in a terminal, you will see your code runs.
The problem you hit is because of the Delve and vscode debug console. The vscode debug console doesn't support read from stdin. You can check this issue: Cannot debug programs which read from STDIN for details.
No need for 'e'. Replace it with an underscore and remove the print statement.
import (
"fmt"
"os"
)
func main() {
fmt.Print("Enter text: \n")
var input string
_, err := fmt.Scanln(&input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(input)
}
For the record. If you want to input a numeric value, fmt.Scan stores the value in a variable as a string, if you would like perform any mathematical operation with it, you need to convert it either to int or float. A quick example:
func main() {
fmt.Println("Type your age: ")
var input string
_, err := fmt.Scanln(&input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Printf("%T\n", input) // outputs string
inputInt,_ := strconv.Atoi(input)
fmt.Printf("%T\n", inputInt) // outputs int
fmt.Printf("You were born in %d\n", 2021-inputInt)
}
Took me a while to figure it out, hope it helps!
Is it possible to read a commands output with its color attributes. I mean, can we read the actual escape sequences.
for instance;
A command output is red colored:
Hello
I want to read it as :
\033[31;1;4mHello\033[0m
Currently I am reading it like:
func stat(hash string) string {
cmd := exec.Command("git", "show", "--stat", hash)
out, err := cmd.Output()
if err != nil {
return err.Error()
}
return string(out)
}
Use the github.com/creack/pty library to run the command in a pty
This works for me
The escape sequences are visible in the output
package main
import (
"github.com/creack/pty"
"io"
"os"
"os/exec"
)
func main() {
hash := os.Args[1]
cmd := exec.Command("git", "show", "--stat", hash)
f, err := pty.Start(cmd)
if err != nil {
panic(err)
}
io.Copy(os.Stdout, f)
}
I'm trying to execute a command that asks for several inputs for example if you try to copy a file from local device to the remote device we use scp test.txt user#domain:~/ then it asks us for the password. What I want is I want to write a go code where I provide the password in the code itself for example pass:='Secret Password'. Similarly, I have CLI command where it asks us for several things such as IP, name, etc so I need to write a code where I just declare all the values in the code itself and when I run the code it doesn't ask anything just take all the inputs from code and run CLI command in case of copying file to remote it should not ask me for password when I run my go binary it should directly copy my file to remote decide.
func main() {
cmd := exec.Command("scp", "text.txt", "user#domain:~/")
stdin, err := cmd.StdinPipe()
if err = cmd.Start(); err != nil {
log.Fatalf("failed to start command: %s", err)
}
io.WriteString(stdin, "password\n")
if err = cmd.Wait(); err != nil {
log.Fatalf("command failed: %s", err)
}
}
If I use this code it is stuck on user#domain's password:
And no file is copied to the remote device.
Solution 1
You can bypass this with printf command
cmd := "printf 'John Doe\nNew York\n35' | myInteractiveCmd"
out, err := exec.Command("bash", "-c", cmd).Output()
Solution 2
You can use io.Pipe(). Pipe creates a synchronous in-memory pipe and you can write your answers into io.Writer and your cmd will read from io.Reader.
r, w := io.Pipe()
cmd := exec.Command("myInteractiveCmd")
cmd.Stdin = r
go func() {
fmt.Fprintf(w, "John Doe\n")
fmt.Fprintf(w, "New York\n")
fmt.Fprintf(w, "35\n")
w.Close()
}()
cmd.Start()
cmd.Wait()
Testing info
To test this I wrote cmd which asks for name, city, age and writes the result in file.
reader := bufio.NewReader(os.Stdin)
fmt.Print("Name: ")
name, _ := reader.ReadString('\n')
name = strings.Trim(name, "\n")
...
One way to go about this is to use command-line flags:
package main
import (
"flag"
"fmt"
"math"
)
func main() {
var (
name = flag.String("name", "John", "Enter your name.")
ip = flag.Int("ip", 12345, "What is your ip?")
)
flag.Parse()
fmt.Println("name:", *name)
fmt.Println("ip:", *ip)
}
Now you can run the program with name and ip flags:
go run main.go -name="some random name" -ip=12345678910`
some random name
ip: 12345678910
This channel is a good resource—he used to work for the Go team and made tons of videos on developing command-line programs in the language. Good luck!
I come across this question when trying to run the linux make menuconfig through golang os/exec.
To accomplish what you are trying to achieve try to set the cmd.Stdin to os.Stdin. Here is a working example:
package main
import (
"fmt"
"os"
"os/exec"
)
type cmdWithEnv struct {
pwd string
command string
cmdArgs []string
envs []string
}
func runCommand(s cmdWithEnv) error {
cmd := exec.Command(s.command, s.cmdArgs...)
if len(s.pwd) != 0 {
cmd.Dir = s.pwd
}
env := os.Environ()
env = append(env, s.envs...)
cmd.Env = env
fmt.Printf("%v\n", cmd)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin // setting this allowed me to interact with ncurses interface from `make menuconfig`
err := cmd.Start()
if err != nil {
return err
}
if err := cmd.Wait(); err != nil {
return err
}
return nil
}
func buildPackage() {
makeKernelConfig := cmdWithEnv{
pwd: "linux",
command: "make",
cmdArgs: []string{"-j12", "menuconfig"},
envs: []string{"CROSS_COMPILE=ccache arm-linux-gnueabihf-", "ARCH=arm"},
}
runCommand(makeKernelConfig)
}
func main() {
buildPackage()
}
Let's say I want to run 'ls' in a go program, and store the results in a string. There seems to be a few commands to fork processes in the exec and os packages, but they require file arguments for stdout, etc. Is there a way to get the output as a string?
There is an easier way now:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
out, err := exec.Command("date").Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("The date is %s\n", out)
}
Where out is the standard output. It's in the format []byte, but you can change it to string easily with:
string(out)
You can also use CombinedOutput() instead of Output() which returns standard output and standard error.
exec.Command
To get both stdout and stderr into separate strings, you can use byte buffers like so:
cmd := exec.Command("date")
var outb, errb bytes.Buffer
cmd.Stdout = &outb
cmd.Stderr = &errb
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
fmt.Println("out:", outb.String(), "err:", errb.String())
cmd := exec.Command("ls", "-al")
output, _ := cmd.CombinedOutput()
fmt.Println(string(output))
or
cmd := exec.Command(name, arg...)
stdout, err := cmd.StdoutPipe()
cmd.Stderr = cmd.Stdout
if err != nil {
return err
}
if err = cmd.Start(); err != nil {
return err
}
for {
tmp := make([]byte, 1024)
_, err := stdout.Read(tmp)
fmt.Print(string(tmp))
if err != nil {
break
}
}
I used this with a recent version of GO (~1.11)
// CmdExec Execute a command
func CmdExec(args ...string) (string, error) {
baseCmd := args[0]
cmdArgs := args[1:]
log.Debugf("Exec: %v", args)
cmd := exec.Command(baseCmd, cmdArgs...)
out, err := cmd.Output()
if err != nil {
return "", err
}
return string(out), nil
}
// Usage:
// out, err := CmdExec("ls", "/home")
Two options, depending on the paradigm you prefer:
os.ForkExec()
exec.Run()
Use exec.Run, passing Pipe for stdout. Read from the pipe that it returns.
If you are wanting string output, strings.Builder is more efficient [1] than
bytes.Buffer:
package main
import (
"os/exec"
"strings"
)
func main() {
c, b := exec.Command("go", "version"), new(strings.Builder)
c.Stdout = b
c.Run()
print(b.String())
}
https://golang.org/pkg/bytes#Buffer.String
Edit: This answer is obsolete. Please see Fatih Arslan's answer below.
Use exec.Run by specifying Pipe as the stdout (and stderr if you want). It will return cmd, which contains an os.File in the Stdout (and Stderr) fields. Then you can read it using for example ioutil.ReadAll.
Example:
package main
import (
"exec";
"io/ioutil";
)
func main() {
if cmd, e := exec.Run("/bin/ls", nil, nil, exec.DevNull, exec.Pipe, exec.MergeWithStdout); e == nil {
b, _ := ioutil.ReadAll(cmd.Stdout)
println("output: " + string(b))
}
}