Go Recipes - Gosh Tips

Table of Contents

Gosh Tips

These recipes offer some tips for getting the most from gosh and how to fix any problems you might have.

This uses the param package for handling parameters; see here for notes on features such as help messages offered through the param package.

For other recipes see here.

Installing gosh

Summary

To install gosh run the following command:

go install github.com/nickwells/utilities/gosh@latest

This will install the gosh command in the $GOPATH/bin directory (by default: $HOME/go/bin). Make sure you have that directory added to your PATH.

Discussion

The gosh command is self contained but there are some extras which you can also install.

You can use the newly installed gosh command to check that it has everything it needs as follows:

gosh -pre-check

This will not generate a program but will instead check that the commands that gosh needs are available and recommends any fixes.

One thing that the pre-check might warn about is if you haven't installed snippets (pre-written code fragments). These are not strictly necessary, you can run gosh without them, but they can be useful.

To install the snippets you will need to choose where you want to copy them. Gosh comes with some standard directories which it will search for snippets, the following command will list them:

gosh -snippet-list-dir

Choose one of these directories and set the SD shell variable to the directory name (this is just for the purpose of the installation commands below).

The recommended way of installing the snippets is to install the gosh.snippet command and then use that to install the standard snippets. The following command will create the snippet directory if it doesn't already exist and will copy in the standard set of snippets, moving aside any differing snippets of the same name.

go install github.com/nickwells/utilities/gosh.snippet@latest
gosh.snippet -to $SD -install

If you run it without the install parameter it will just compare the standard snippet set with the contents of the directory you have given. If you are installing the snippets for the first time then it should report them as all being new.

Alternatively you can copy the snippets directly. If you git clone the utilities repository you can find the _snippets directory under the gosh.snippet directory. The following commands (on Linux) will create the snippets directory and copy in the standard snippets, when run from the gosh.snippet source directory.

mkdir --parents $SD
cp -r --suffix=.orig --backup ./_snippets/* $SD

Once you have installed the snippets, using either of the methods above, you should clear out any copies generated of snippets you may have set up before. You only need to do this if you already had older versions of the snippets in target directory. A useful command for helping with this is the findCmpRm command which can be installed as follows.

go install github.com/nickwells/utilities/findCmpRm@latest
findCmpRm -dir $SD -recursive -extension .orig

Getting help

Comprehensive help is available by simply passing the help parameter as follows:

gosh -help

See here for more details.

Running code

Summary

The 'exec' parameter (or its synonym 'e') gives code for gosh to run.

gosh -e 'fmt.Println("Hi!")'

This will print "Hi!".

Discussion

You can have multiple lines of code by repeating the exec parameter; they are added in the order they appear on the command line. Alternatively you can have multiple lines in the same parameter; make sure that you've quoted it correctly so the shell doesn't think you've finished the command early. You can separate Go statements with semi-colons either; these are not normally needed by Go and the gofmt (or goimports) command will replace them with new lines but it can make it easier when entering Go code at the command line. The following are all equivalent:

gosh -e 'fmt.Print("Greetings,")' -e 'fmt.Println(" Universe!")'
gosh -e 'fmt.Print("Greetings,"); fmt.Println(" Universe!")'
gosh -e 'fmt.Print("Greetings,")
fmt.Println(" Universe!")'

Code given to gosh through the 'exec' parameter will appear in the 'main' function. If you want to declare a function you will need to add it to the global scope using the 'global' (or 'g') parameter. The following code declares a function that prints "Hello, World!" and then calls it.

gosh -g 'func F() { fmt.Print("Hello, World!\n") }' -e 'F()'

Code sections

Summary

When gosh builds the code to be run it builds it in sections. There are the boilerplate sections that gosh generates itself and there are sections that gosh populates with code that you have given. The code sections are listed in a help note; run the following code to see this note.

gosh -help-note 'Gosh - code sections'

The sections are each separate and you can populate them independently. Code added to a section is added in the order it appears on the command line. For instance:

gosh -after 'fmt.Print("a1")' \
     -exec 'fmt.Print("e1")' \
     -before 'fmt.Print("b1")' \
     -after 'fmt.Print("a2")' \
     -exec 'fmt.Print("e2")' \
     -before 'fmt.Print("b2")'

will print "b1b2e1e2a1a2" so you can see that the sections are grouped independently. Code in the 'global' section is for global variables and functions, it will appear outside the main function.

The different sections are present because gosh can run code in a loop where it reads from standard input (or files) a line at a time and then the code in the 'before' section appears before the loop, the 'after' section comes after the loop and the 'exec' section runs inside the loop.

Shortcuts for printing

Summary

There are some shortcuts that gosh offers to make it faster and easier to write scripts. Printing is a common task and gosh offers some parameters to make this simpler. The following are equivalent:

gosh -pln '"Hi!"'
gosh -e 'fmt.Println("Hi!")'

… and …

gosh -pf '"pi = %.9f\n", math.Pi'
gosh -e 'fmt.Printf("pi = %.9f\n", math.Pi)'

There are similar shortcuts for printing in the various code sections; see the help message for details.

Snippets

Summary

gosh allows you to have useful code fragments called snippets in files and to import them into the program being built. These can either be given as complete filenames or, more usefully, as shorter names in standard directories. To see all the snippets available pass the snippet-list parameter as follows:

gosh -snippet-list

You can write your own snippets, just create a file containing Go code into one of the snippet directories and you can start using it. It's worth adding some documentary comments as well though if only so you'll remember what it was for. The standard snippets are all documented and use the meaningful comment tags to help gosh check that snippets are being used correctly.

When writing a snippet it is worth following the convention of starting the names of any variables you create with a double underscore. This will mean you are unlikely to clash with the names of any variables that you'll use in your gosh scripts and it also avoids clashing with variables that gosh itself creates (these all start with a single underscore).

To see the gosh notes on snippets the following gosh command will show you just what you need.

gosh -help-notes 'Gosh - snippets,Gosh - snippet comments,Gosh - snippet directories'

Looping over input

Summary

A common task at the command line is to read the standard output of a previous command, do something with it and write the transformed input onto the standard output. In Unix terminology such programs are called filters. Gosh offers some parameters to help write filters in Go at the command line.

The 'run-in-readloop' parameter (or 'n' for short) will use a bufio.Scanner called '_l' to read from standard input (by default) and the lines (with any newline removed) are then available to you using the 'Text' method on the scanner.

gosh -n -b 'count := 0' -e 'count++' -a-pln count

This will count the lines like the Linux 'wc' command.

If you want to split the line into parts run gosh passing the 'split-line' (or 's') parameter. The default behaviour splits the line into words separated by one or more whitespace characters. You can change the regular expression used to split the line by passing the 'split-pattern' (or 'sp') parameter. The parts are written into a slice of strings called '_lp'.

If you want to run the script over a list of files instead of over the standard input these files can be provided after the '–' parameter. In this case the script will loop over each file in turn. In this case the before-inner and after-inner code sections become useful as you can use them to insert code af=round the inner loop.

Missing goimports command

Summary

If you have goimports properly installed on your machine then gosh will run it to format the program before running it. This will add any import statements you need.

There can be a number of problems:

  • goimports is not installed
  • it is installed but it isn't in any directory in the PATH
  • it's deliberately not installed

If it isn't installed then you can install it by following the instructions at golang.org/x/tools/cmd/goimports.

If it is not in your PATH then you can tell gosh the formatter to use as follows:

gosh -e 'fmt.Print("Hello,")
     fmt.Println(" World!")' -formatter /home/me/go/bin/goimports

If it's not installed and you don't want to install it then you will need to give all the imports explicitly.

gosh -e 'var v rune; fmt.Println(unsafe.Sizeof(v))' -import unsafe -import fmt

Discussion

The goimports program is a very useful tool - it will format your program just like gofmt but it will also find any packages that haven't been imported and it will add import statements for them. It can sometimes pick the wrong version of a module and so you might want to provide the package import explicitly even if it is available.

If goimports isn't available for some reason, you can use the gopls program as an alternative as follows:

gosh -e 'fmt.Println("Hello")' -formatter gopls -formatter-args="imports","-w"

The formatter and formatter-args parameters can be added to the gosh config file so that they don't need to be repeated every time. To find the config file for gosh, you can run it as follows:

gosh -help-show sources

goimports isn't finding my package

Summary

You have goimports installed and you've called some function from the package but goimports isn't finding it; what's the problem?

The first thing to check is that you've got the function name correct and the right arguments, that's a common reason for failing to import the right package.

Another reason, if the package has just been uploaded, is that the Go proxy might not have been updated yet. You can either wait a few minutes or else import the package by hand through the -import (or -I) parameter.

Troubleshooting the generated program

Summary

If the code you supply doesn't behave as you think it should you can always examine the generated program. If there is an error while running the program or it can't be run at all then it will show the name of the directory where gosh built your program. This will not have been cleared up as usual and so you can take a look at the generated code. Alternatively you can run gosh with the show-filename parameter.

gosh -pln '"Hello World!"' -show-filename

This will run the program as normal but will report the directory and file names and will not clear the directory when it finishes so you can examine the code.

When looking at the code it might be useful to have gosh provide comments which will make it clearer which lines are written by gosh and which you have supplied. The add-comments parameter makes gosh comment the code.

Alternatively, you can run gosh with the edit parameter and it will open the gosh-generated program in your favourite editor (as given by the EDITOR environment variable) just before running it and you can correct any problems or just take a look at what gets generated.

gosh -pln '"Hello World!"' -edit

You can also make gosh loop around the edit/build/run steps until you are happy with the result. To achieve this call gosh with the edit-repeat parameter.

gosh -pln '"Hello World!"' -edit-repeat

What is gosh doing

Summary

If you run gosh as usual but add a "-verbose" parameter it will run the program but it will also print out messages explaining the steps it is taking and some timing information.

gosh -pln '"Hello, World!"' -verbose

Useful shell aliases

Summary

Your shell may offer an alias feature so that you can access commands more quickly, here are a few suggestions for some aliases that speed up common gosh tasks. These work in zsh and bash at least. You might need to change the alias names if they clash with any existing aliases. Note that the aliases use the long parameter names for documentation purposes; the following comments give an equivalent short-form.

alias g.e="gosh -exec"		# -e
alias g.es="gosh -exec-snippet"	# -e-s
alias g.p="gosh -println"		# -pln
alias g.pf="gosh -printf"		# -pf
alias g.h="gosh -help"		# -help
alias g.has="gosh -help-all-short"	# -help-as
alias g.hf="gosh -help-full"		# -help-f

Using gosh in shebang scripts

gosh can be used as the interpreter for shebang scripts, that is executable text files with the first line starting '#!'.

Summary

You can write Go code in a file and put:

#!gosh -shebang

at the very top of the file and gosh will read the rest of the file as a script to be built into a main func and run it.

Discussion

Although the code above shows just the command name, it is safest to put the whole path to gosh as the interpreter name. Be aware that this might make the script less portable to other hosts where the path to gosh is different.

The way to pass gosh params to a shebang script is through comments at the start of the script. Any line immediately following the '#!' line which starts with '#' will be stripped from the code to be compiled. If the line starts '#gosh.param:' then the remainder of the line is treated like a line in a parameter file.

The most robust way of writing a shebang script using gosh is to add comment lines at the front controlling how it works. Some suggested parameters are:

import
to give the packages to import
no-auto-import
to speed up execution and remove dependency on other tools
no-go-mod-tidy
to speed up execution and remove dependency on other tools
set-go-cmd
to let users without Go in their path to run the script
set-executable-name
to make it easier to find the command in ps listings
show-filename=false
to undo the effect of the set-executable-name
dont-loop-on-args
to stop gosh from looping over the arguments
no-more-params
to prevent any arguments to the script being passed to gosh

Author: Nick Wells

Created: 2023-08-28 Mon 19:43

Validate