Golang: Command Prompt - go

I am writing a personal tool which gives me a prompt and lets me execute useful commands. Like bash but its not a shell program
I want an input prompt like Bash
If you enter nothing, just print a new line and scan for input again
If you enter the hotkey Ctrl + D, terminate the program
It should be like this
[user#computer ~]$ go build
[user#computer ~]$ ./myapp
$
$ command
.. do command
$ (Ctrl + D hotkey)
[user#computer ~]$
I tried using Scanln but it didn't accept spaces which was a huge problem
EDIT: here is what I implemented
func main() {
var input string
fmt.Println(banners.Gecko1)
fmt.Print("$ ")
fmt.Scanln(&input)
}
Here is the console:
[user#pc MyTool]$ go build
[user#pc MyTool]$ ./MyTool
-----ENTER COMMANDS
$ term1 term2 term3 term4 term5
[user#pc MyTool]$ erm2 term3 term4 term5
bash: erm2: command not found
[user#pc MyTool]$
as you can see, everything after term1 is ignored and somehow passed to bash...

I think that's how scanln is supposed to work. The docs state that it "scans text from standard input, storing successive space-separated values into successive arguments." Alternatively, use the bufio package if you want to read one line at a time. You can refer to the sample code below:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
input_reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("$ ")
line, _ := input_reader.ReadString('\n')
line = strings.Replace(line, "\n", "", -1)
fmt.Println(line)
}
}

Related

How to continue program execution when using bufio.Scanner in golang

Forgive me I am starting out in Go and I am learning about the bufio package but every time I use the Scanner type the command line is stuck on the input and does not continue with normal program flow. I have tried pressing Enter but it just keeps going to a new line.
Here is my code.
/*
Dup 1 prints the text of each line that appears more than
once in the standard input, proceeded by its count.
*/
package main
import(
"bufio"
"fmt"
"os"
)
func main(){
counts := make(map[string]int)
fmt.Println("Type Some Text")
input := bufio.NewScanner(os.Stdin)
for input.Scan(){
counts[input.Text()]++
}
//NOTE: Ignoring potential Errors from input.Err()
for line,n := range counts{
if n > 1{
fmt.Printf("%d \t %s \n",n,line)
}
}
}
You have a for loop which reads lines from the standard input. This loop will run as long as os.Stdin doesn't report io.EOF (that's one case when Scanner.Scan() would return false). Normally this won't happen.
If you want to "simulate" the end of input, press Ctrl+Z on Windows, or Ctrl+D on Linux / unix systems.
So enter some lines (each "closed" by Enter), and when you're finished, press the above mentioned key.
Example output:
Type Some Text
a
a
bb
bb
bbb <-- CTRL+D pressed here
2 a
2 bb
Another option would be to use a "special" word for termination, such as "exit". It could look like this:
for input.Scan() {
line := input.Text()
if line == "exit" {
break
}
counts[line]++
}
Testing it:
Type Some Text
a
a
bb
bb
bbb
exit
2 a
2 bb

Why does slicing the result of a ReadString() operation result in weird output?

package main
import (
"bufio"
"fmt"
"os"
)
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("Input: ")
input, _ := reader.ReadString('\n')
fmt.Println("thing\n"[:5] + "\"")
fmt.Println(input[:len(input)-1] + "\"")
return
}
Running the code:
Input: thing
thing"
"hing
Why does the second concatenation behave oddly? It should produce identical results, assuming the ReadString() operation returns a string with a newline at the end. Please explain what is going on here.
That's because you're presumably on windows.
The actual input you make from your keyboard is not thing\n but thing\r\n
So when you do fmt.Println(input[:len(input)-1] + "\"") it only truncates the latest \n and leaves \r.
So the terminal prints thing, then reaches \r that returns carriage to the beginning of the string, then you print a double quote. But the carriage is in the first position now, and it effectively overwrites the first character of the line, leaving you with "hing

Golang Command-Line String Flag

I'm trying to get command line flags working with strings in golang. Here is my main/main.go file:
package main
import (
"flag"
"log"
)
func main() {
flagString := flag.String("string", "foo", "Enter this string and have it printed back out.")
log.Println("You entered ", *flagString)
}
This simply takes the flag from the command line and prints it out, with default value "foo".
I enter the following into the command prompt after building the project, trying to make it print out bar:
> main -string=bar
(log time+date) You entered foo
> main -string="bar"
(log time+date) You entered foo
Is there something wrong with my code or am I entering it into the command prompt incorrectly?
By the way, I am running Windows 10.
After calling flag.String(...), you need to simply call flag.Parse().
In your example:
package main
import (
"flag"
"log"
)
func main() {
flagString := flag.String("string", "foo", "Enter this string and have it printed back out.")
flags.Parse()
log.Println("You entered ", *flagString)
}

How can I print the counts of the lines that are introduced via stdin?

I have the following program that I want to show how many times a specific introduced line appears from stdin:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
counts[input.Text()]++
}
// NOTE: ignoring potential errors from input.Err()
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
When I run the program it allows me to enter strings, but even when I press enter I don't get any feedback.
What am I missing here? I believe it gets stuked in the first for.
Try this - save the following text in /tmp/input.txt or other local file:
Line A
Line B
Line B
Line A
Line C
Line B
Now pipe the contents of that file as standard input to your program - for example in /tmp/q.go:
cat /tmp/input.txt | go run /tmp/q.go
The output should be:
$ cat /tmp/input.txt | go run /tmp/q.go
2 Line A
3 Line B
your code first gets all lines, then prints the result at the end (EOF).
1 - if you need feedback for each line when you press Enter: edit your first loop:
for input.Scan() {
txt := input.Text()
counts[txt]++
fmt.Println("counts[", txt, "] =", counts[txt])
}
like this working sample code:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
txt := input.Text()
counts[txt]++
fmt.Println("counts[", txt, "] =", counts[txt])
}
// NOTE: ignoring potential errors from input.Err()
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
2- if you want the result at the end of data entry, you have two options:
Option A: press EOF at the end of lines in terminal (command prompt): Ctrl+Z then Enter in Windows, or Ctrl+D in Linux.
Option B: save all text data in one file like "lines.txt" and then run your go binary file with this file as input (Redirecting input) like this:
Windows:
main.exe < lines.txt
Linux:
./main < lines.txt
The place where you are going wrong is:
You are not giving any condition for the first for loop to terminate. This leaves you with the two options suggested by Volker. Either way, you are essentially providing the program with an EOF, for it to know when to stop scanning for input.
Now, the chances are this might not look very clean to you. In that case (and only in that case), you can proceed by introducing a termination condition inside the input for loop:
if input.Text() == "" {
break;
}
This leads to the execution to reach the second for loop as soon as you enter two new-line characters. Quite handy if you would like to prompt the user on the beginning of the program to Hit enter twice to show results, or something of that sorts.
Cheers!

Go: embed bash script in binary

I'm trying to cross compile a Go program that will execute a bash script. Is it possible to embed the bash script in the binary?
I've referenced:
Golang serve static files from memory
Not sure if this applies to executing bash scripts though. Am I missing something here? Some clarification or pointers will be very helpful, thanks!
Since bash can execute scripts from stdin, you just send your script to the bash command via command.Stdin.
An example without go embed:
package main
import (
"bytes"
"fmt"
"os/exec"
"strings"
)
var script = `
echo $PWD
pwd
echo "-----------------------------------"
# print process
ps aux | grep code
`
func main() {
c := exec.Command("bash")
c.Stdin = strings.NewReader(script)
b, e := c.Output()
if e != nil {
fmt.Println(e)
}
fmt.Println(string(b))
}
With go 1.16 embed (https://golang.org/pkg/embed/):
package main
import (
"bytes"
_ "embed"
"fmt"
"os/exec"
"strings"
)
//go:embed script.sh
var script string
func main() {
c := exec.Command("bash")
c.Stdin = strings.NewReader(script)
b, e := c.Output()
if e != nil {
fmt.Println(e)
}
fmt.Println(string(b))
}
Bonus
Passing parameters to your script with -s -.
The following example will pass -la /etc to the script.
package main
import (
"fmt"
"os/exec"
"strings"
)
func main() {
// pass parameters to your script as a safe way.
c := exec.Command("sh", "-s", "-", "-la", "/etc")
// use $1, $2, ... $# as usual
c.Stdin = strings.NewReader(`
echo $#
ls $1 "$2"
`)
b, e := c.Output()
if e != nil {
fmt.Println(e)
}
fmt.Println(string(b))
}
Playground: https://go.dev/play/p/T1lMSrXcOIL
You can actually directly interface with the system shell in Go. Depending on what's in your bash script you can probably convert everything completely to go. For example things like handling files, extracting archives, outputting text, asking for user input, downloading files, and so much more can be done natively in Go. For anything you absolutely need the shell for you can always use golang.org/pkg/os/exec.
I wrote a snippet that demonstrates a really simple Go based command shell. Basically it pipes input, output, and error between the user and the shell. It can be used interactively or to directly run most shell commands. I'm mentioning it here mostly to demonstrate Go's OS capabilities. Check it out: github.com/lee8oi/goshell.go
Did you try writing the stream-data (according to the reference go-bindata provides a function that returns []byte) into a temporary file?
see: http://golang.org/pkg/io/ioutil/#TempFile
you can then execute it with a syscall
http://golang.org/pkg/syscall/#Exec
where the first argument needs to be a shell.

Resources