Skip to content

Commit

Permalink
Add support for ENVIRON special array
Browse files Browse the repository at this point in the history
The difference between the name-value pair slice and the "key=value"
format is unfortunate, but I think consistency with config.Vars is
important, and also I've never liked os.Environ()'s format because you
usually have to strings.Split it anyway. Besides, in most cases the
default will be fine.

Fixes #73
  • Loading branch information
benhoyt committed Dec 22, 2021
1 parent cbe2629 commit f44d0cd
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 1 deletion.
23 changes: 23 additions & 0 deletions interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ type Config struct {
// Exec args used to run system shell. Typically, this will
// be {"/bin/sh", "-c"}
ShellCommand []string

// List of name-value pairs to be assigned to the ENVIRON special
// array, for example []string{"USER", "bob", "HOME", "/home/bob"}.
// If nil (the default), values from os.Environ() are used.
Environ []string
}

// ExecProgram executes the parsed program using the given interpreter
Expand All @@ -204,6 +209,9 @@ func ExecProgram(program *Program, config *Config) (int, error) {
if len(config.Vars)%2 != 0 {
return 0, newError("length of config.Vars must be a multiple of 2, not %d", len(config.Vars))
}
if len(config.Environ)%2 != 0 {
return 0, newError("length of config.Environ must be a multiple of 2, not %d", len(config.Environ))
}

p := &interp{program: program}

Expand Down Expand Up @@ -252,6 +260,21 @@ func ExecProgram(program *Program, config *Config) (int, error) {
}
}

// Setup ENVIRON from config or environment variables
environIndex := program.Arrays["ENVIRON"]
if config.Environ != nil {
for i := 0; i < len(config.Environ); i += 2 {
p.setArrayValue(ScopeGlobal, environIndex, config.Environ[i], numStr(config.Environ[i+1]))
}
} else {
for _, kv := range os.Environ() {
eq := strings.IndexByte(kv, '=')
if eq >= 0 {
p.setArrayValue(ScopeGlobal, environIndex, kv[:eq], numStr(kv[eq+1:]))
}
}
}

// Setup system shell command
if len(config.ShellCommand) != 0 {
p.shellCommand = config.ShellCommand
Expand Down
27 changes: 27 additions & 0 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,33 @@ func TestFlushError(t *testing.T) {
}
}

func TestEnviron(t *testing.T) {
os.Setenv("GOAWK_TEN", "10") // to test that ENVIRON[x] is numeric string
src := `
BEGIN {
n = 0
for (k in ENVIRON)
n++
print(n, ENVIRON["USER"], ENVIRON["GOAWK_TEN"] < 2)
}`
expected := fmt.Sprintf("%d %s 0\n", len(os.Environ()), os.Getenv("USER"))
testGoAWK(t, src, "", expected, "", nil, nil)

expected = "2 bob 0\n"
testGoAWK(t, src, "", expected, "", nil, func(config *interp.Config) {
config.Environ = []string{"USER", "bob", "GOAWK_TEN", "10"}
})

expected = "0 1\n"
testGoAWK(t, src, "", expected, "", nil, func(config *interp.Config) {
config.Environ = []string{}
})

testGoAWK(t, src, "", "", "length of config.Environ must be a multiple of 2, not 3", nil, func(config *interp.Config) {
config.Environ = []string{"b", "a", "d"}
})
}

func benchmarkProgram(b *testing.B, funcs map[string]interface{},
input, expected, srcFormat string, args ...interface{},
) {
Expand Down
3 changes: 2 additions & 1 deletion parser/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ func (p *parser) initResolve() {
p.varTypes = make(map[string]map[string]typeInfo)
p.varTypes[""] = make(map[string]typeInfo) // globals
p.functions = make(map[string]int)
p.arrayRef("ARGV", Position{1, 1}) // interpreter relies on ARGV being present
p.arrayRef("ARGV", Position{1, 1}) // interpreter relies on ARGV being present
p.arrayRef("ENVIRON", Position{1, 1}) // and ENVIRON
p.multiExprs = make(map[*MultiExpr]Position, 3)
}

Expand Down

0 comments on commit f44d0cd

Please sign in to comment.