shell

package
v0.21.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2024 License: BSD-3-Clause Imports: 5 Imported by: 2

Documentation

Overview

Package shell supports splitting and joining of shell command strings.

The Split function divides a string into whitespace-separated fields, respecting single and double quotation marks as defined by the Shell Command Language section of IEEE Std 1003.1 2013. The Quote function quotes characters that would otherwise be subject to shell evaluation, and the Join function concatenates quoted strings with spaces between them.

The relationship between Split and Join is that given

fields, ok := shell.Split(shell.Join(ss))

the following relationship will hold:

fields == ss && ok

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Join

func Join(ss []string) string

Join quotes each element of ss with Quote and concatenates the resulting strings separated by spaces.

func Quote

func Quote(s string) string

Quote returns a copy of s in which shell metacharacters are quoted to protect them from evaluation.

func Split

func Split(s string) ([]string, bool)

Split partitions s into tokens divided on space, tab, and newline characters using a *Scanner. Leading and trailing whitespace are ignored.

The Boolean flag reports whether the final token is "valid", meaning there were no unclosed quotations in the string.

Types

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

A Scanner partitions input from a reader into tokens divided on space, tab, and newline characters. Single and double quotation marks are handled as described in http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/creachadair/mds/shell"
)

func main() {
	const input = `a "free range" exploration of soi\ disant novelties`
	s := shell.NewScanner(strings.NewReader(input))
	sum, count := 0, 0
	for tok := range s.Each {
		count++
		sum += len(tok)
	}
	fmt.Println(len(input), count, sum, s.Complete(), s.Err())
}
Output:

51 6 43 true EOF

func NewScanner

func NewScanner(r io.Reader) *Scanner

NewScanner returns a Scanner that reads input from r.

func (*Scanner) Complete

func (s *Scanner) Complete() bool

Complete reports whether the current token is complete, meaning either that it is unquoted or that its quotes were balanced.

func (*Scanner) Each

func (s *Scanner) Each(f func(tok string) bool)

Each is a range function that calls f for each token in the scanner. It continues until the input is exhausted, f returns false, or an error other than io.EOF occurs. Use the Err method to check for an error.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/creachadair/mds/shell"
)

func main() {
	const input = `a\ b 'c d' "e f's g" stop "go directly to jail"`
	s := shell.NewScanner(strings.NewReader(input))
	for tok := range s.Each {
		fmt.Println(tok)
		if tok == "stop" {
			break
		}
	}
	if err := s.Err(); err != nil {
		log.Fatal(err)
	}
}
Output:

a b
c d
e f's g
stop

func (*Scanner) Err

func (s *Scanner) Err() error

Err returns the error, if any, that resulted from the most recent action.

func (*Scanner) Next

func (s *Scanner) Next() bool

Next advances the scanner and reports whether there are any further tokens to be consumed.

func (*Scanner) Reset added in v0.17.2

func (s *Scanner) Reset(r io.Reader)

Reset discards the current token (if any) and all remaining input from s, and resets its internal state to consume tokens from r.

Reset retains the internal buffers of s, so when processing multiple consecutive inputs, it will be more memory-efficient to read to allocate a single scanner and reset it for each reader in sequence.

func (*Scanner) Rest

func (s *Scanner) Rest() io.Reader

Rest returns an io.Reader for the remainder of the unconsumed input in s. After calling this method, Next will always return false. The remainder does not include the text of the current token at the time Rest is called.

Example
package main

import (
	"fmt"
	"io"
	"log"
	"strings"

	"github.com/creachadair/mds/shell"
)

func main() {
	const input = `things 'and stuff' %end% all the remaining stuff`
	s := shell.NewScanner(strings.NewReader(input))
	for tok := range s.Each {
		if tok == "%end%" {
			fmt.Print("found marker; ")
			break
		}
	}
	rest, err := io.ReadAll(s.Rest())
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(rest))
}
Output:

found marker; all the remaining stuff

func (*Scanner) Split

func (s *Scanner) Split() []string

Split returns the remaining tokens in s, not including the current token if there is one. Any tokens already consumed are still returned, even if there is an error.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/creachadair/mds/shell"
)

func main() {
	const input = `cmd -flag=t -- foo bar baz`

	s := shell.NewScanner(strings.NewReader(input))
	for s.Next() {
		if s.Text() == "--" {
			fmt.Println("** Args:", strings.Join(s.Split(), ", "))
		} else {
			fmt.Println(s.Text())
		}
	}
}
Output:

cmd
-flag=t
** Args: foo, bar, baz

func (*Scanner) Text

func (s *Scanner) Text() string

Text returns the text of the current token, or "" if there is none.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL