os: implement os.ExpandEnv
Этот коммит содержится в:
родитель
94b075e423
коммит
db0efc52c7
4 изменённых файлов: 292 добавлений и 9 удалений
120
src/os/env.go
120
src/os/env.go
|
@ -1,14 +1,121 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// General environment variables.
|
||||||
|
|
||||||
package os
|
package os
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"internal/testlog"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Expand replaces ${var} or $var in the string based on the mapping function.
|
||||||
|
// For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv).
|
||||||
|
func Expand(s string, mapping func(string) string) string {
|
||||||
|
var buf []byte
|
||||||
|
// ${} is all ASCII, so bytes are fine for this operation.
|
||||||
|
i := 0
|
||||||
|
for j := 0; j < len(s); j++ {
|
||||||
|
if s[j] == '$' && j+1 < len(s) {
|
||||||
|
if buf == nil {
|
||||||
|
buf = make([]byte, 0, 2*len(s))
|
||||||
|
}
|
||||||
|
buf = append(buf, s[i:j]...)
|
||||||
|
name, w := getShellName(s[j+1:])
|
||||||
|
if name == "" && w > 0 {
|
||||||
|
// Encountered invalid syntax; eat the
|
||||||
|
// characters.
|
||||||
|
} else if name == "" {
|
||||||
|
// Valid syntax, but $ was not followed by a
|
||||||
|
// name. Leave the dollar character untouched.
|
||||||
|
buf = append(buf, s[j])
|
||||||
|
} else {
|
||||||
|
buf = append(buf, mapping(name)...)
|
||||||
|
}
|
||||||
|
j += w
|
||||||
|
i = j + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buf == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return string(buf) + s[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandEnv replaces ${var} or $var in the string according to the values
|
||||||
|
// of the current environment variables. References to undefined
|
||||||
|
// variables are replaced by the empty string.
|
||||||
|
func ExpandEnv(s string) string {
|
||||||
|
return Expand(s, Getenv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isShellSpecialVar reports whether the character identifies a special
|
||||||
|
// shell variable such as $*.
|
||||||
|
func isShellSpecialVar(c uint8) bool {
|
||||||
|
switch c {
|
||||||
|
case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAlphaNum reports whether the byte is an ASCII letter, number, or underscore
|
||||||
|
func isAlphaNum(c uint8) bool {
|
||||||
|
return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
// getShellName returns the name that begins the string and the number of bytes
|
||||||
|
// consumed to extract it. If the name is enclosed in {}, it's part of a ${}
|
||||||
|
// expansion and two more bytes are needed than the length of the name.
|
||||||
|
func getShellName(s string) (string, int) {
|
||||||
|
switch {
|
||||||
|
case s[0] == '{':
|
||||||
|
if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == '}' {
|
||||||
|
return s[1:2], 3
|
||||||
|
}
|
||||||
|
// Scan to closing brace
|
||||||
|
for i := 1; i < len(s); i++ {
|
||||||
|
if s[i] == '}' {
|
||||||
|
if i == 1 {
|
||||||
|
return "", 2 // Bad syntax; eat "${}"
|
||||||
|
}
|
||||||
|
return s[1:i], i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", 1 // Bad syntax; eat "${"
|
||||||
|
case isShellSpecialVar(s[0]):
|
||||||
|
return s[0:1], 1
|
||||||
|
}
|
||||||
|
// Scan alphanumerics.
|
||||||
|
var i int
|
||||||
|
for i = 0; i < len(s) && isAlphaNum(s[i]); i++ {
|
||||||
|
}
|
||||||
|
return s[:i], i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getenv retrieves the value of the environment variable named by the key.
|
||||||
|
// It returns the value, which will be empty if the variable is not present.
|
||||||
|
// To distinguish between an empty value and an unset value, use LookupEnv.
|
||||||
func Getenv(key string) string {
|
func Getenv(key string) string {
|
||||||
|
testlog.Getenv(key)
|
||||||
v, _ := syscall.Getenv(key)
|
v, _ := syscall.Getenv(key)
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupEnv retrieves the value of the environment variable named
|
||||||
|
// by the key. If the variable is present in the environment the
|
||||||
|
// value (which may be empty) is returned and the boolean is true.
|
||||||
|
// Otherwise the returned value will be empty and the boolean will
|
||||||
|
// be false.
|
||||||
|
func LookupEnv(key string) (string, bool) {
|
||||||
|
testlog.Getenv(key)
|
||||||
|
return syscall.Getenv(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setenv sets the value of the environment variable named by the key.
|
||||||
|
// It returns an error, if any.
|
||||||
func Setenv(key, value string) error {
|
func Setenv(key, value string) error {
|
||||||
err := syscall.Setenv(key, value)
|
err := syscall.Setenv(key, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -17,12 +124,9 @@ func Setenv(key, value string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unsetenv unsets a single environment variable.
|
||||||
func Unsetenv(key string) error {
|
func Unsetenv(key string) error {
|
||||||
err := syscall.Unsetenv(key)
|
return syscall.Unsetenv(key)
|
||||||
if err != nil {
|
|
||||||
return NewSyscallError("unsetenv", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clearenv deletes all environment variables.
|
// Clearenv deletes all environment variables.
|
||||||
|
@ -30,10 +134,8 @@ func Clearenv() {
|
||||||
syscall.Clearenv()
|
syscall.Clearenv()
|
||||||
}
|
}
|
||||||
|
|
||||||
func LookupEnv(key string) (string, bool) {
|
// Environ returns a copy of strings representing the environment,
|
||||||
return syscall.Getenv(key)
|
// in the form "key=value".
|
||||||
}
|
|
||||||
|
|
||||||
func Environ() []string {
|
func Environ() []string {
|
||||||
return syscall.Environ()
|
return syscall.Environ()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,82 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// testGetenv gives us a controlled set of variables for testing Expand.
|
||||||
|
func testGetenv(s string) string {
|
||||||
|
switch s {
|
||||||
|
case "*":
|
||||||
|
return "all the args"
|
||||||
|
case "#":
|
||||||
|
return "NARGS"
|
||||||
|
case "$":
|
||||||
|
return "PID"
|
||||||
|
case "1":
|
||||||
|
return "ARGUMENT1"
|
||||||
|
case "HOME":
|
||||||
|
return "/usr/gopher"
|
||||||
|
case "H":
|
||||||
|
return "(Value of H)"
|
||||||
|
case "home_1":
|
||||||
|
return "/usr/foo"
|
||||||
|
case "_":
|
||||||
|
return "underscore"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var expandTests = []struct {
|
||||||
|
in, out string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"$*", "all the args"},
|
||||||
|
{"$$", "PID"},
|
||||||
|
{"${*}", "all the args"},
|
||||||
|
{"$1", "ARGUMENT1"},
|
||||||
|
{"${1}", "ARGUMENT1"},
|
||||||
|
{"now is the time", "now is the time"},
|
||||||
|
{"$HOME", "/usr/gopher"},
|
||||||
|
{"$home_1", "/usr/foo"},
|
||||||
|
{"${HOME}", "/usr/gopher"},
|
||||||
|
{"${H}OME", "(Value of H)OME"},
|
||||||
|
{"A$$$#$1$H$home_1*B", "APIDNARGSARGUMENT1(Value of H)/usr/foo*B"},
|
||||||
|
{"start$+middle$^end$", "start$+middle$^end$"},
|
||||||
|
{"mixed$|bag$$$", "mixed$|bagPID$"},
|
||||||
|
{"$", "$"},
|
||||||
|
{"$}", "$}"},
|
||||||
|
{"${", ""}, // invalid syntax; eat up the characters
|
||||||
|
{"${}", ""}, // invalid syntax; eat up the characters
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpand(t *testing.T) {
|
||||||
|
for _, test := range expandTests {
|
||||||
|
result := Expand(test.in, testGetenv)
|
||||||
|
if result != test.out {
|
||||||
|
t.Errorf("Expand(%q)=%q; expected %q", test.in, result, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var global interface{}
|
||||||
|
|
||||||
|
func BenchmarkExpand(b *testing.B) {
|
||||||
|
b.Run("noop", func(b *testing.B) {
|
||||||
|
var s string
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s = Expand("tick tick tick tick", func(string) string { return "" })
|
||||||
|
}
|
||||||
|
global = s
|
||||||
|
})
|
||||||
|
b.Run("multiple", func(b *testing.B) {
|
||||||
|
var s string
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s = Expand("$a $a $a $a", func(string) string { return "boom" })
|
||||||
|
}
|
||||||
|
global = s
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestConsistentEnviron(t *testing.T) {
|
func TestConsistentEnviron(t *testing.T) {
|
||||||
e0 := Environ()
|
e0 := Environ()
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
|
@ -90,3 +166,39 @@ func TestLookupEnv(t *testing.T) {
|
||||||
t.Errorf("smallpox release failed; world remains safe but LookupEnv is broken")
|
t.Errorf("smallpox release failed; world remains safe but LookupEnv is broken")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On Windows, Environ was observed to report keys with a single leading "=".
|
||||||
|
// Check that they are properly reported by LookupEnv and can be set by SetEnv.
|
||||||
|
// See https://golang.org/issue/49886.
|
||||||
|
func TestEnvironConsistency(t *testing.T) {
|
||||||
|
for _, kv := range Environ() {
|
||||||
|
i := strings.Index(kv, "=")
|
||||||
|
if i == 0 {
|
||||||
|
// We observe in practice keys with a single leading "=" on Windows.
|
||||||
|
// TODO(#49886): Should we consume only the first leading "=" as part
|
||||||
|
// of the key, or parse through arbitrarily many of them until a non-=,
|
||||||
|
// or try each possible key/value boundary until LookupEnv succeeds?
|
||||||
|
i = strings.Index(kv[1:], "=") + 1
|
||||||
|
}
|
||||||
|
if i < 0 {
|
||||||
|
t.Errorf("Environ entry missing '=': %q", kv)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := kv[:i]
|
||||||
|
v := kv[i+1:]
|
||||||
|
v2, ok := LookupEnv(k)
|
||||||
|
if ok && v == v2 {
|
||||||
|
t.Logf("LookupEnv(%q) = %q, %t", k, v2, ok)
|
||||||
|
} else {
|
||||||
|
t.Errorf("Environ contains %q, but LookupEnv(%q) = %q, %t", kv, k, v2, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since k=v is already present in the environment,
|
||||||
|
// setting it should be a no-op.
|
||||||
|
if err := Setenv(k, v); err == nil {
|
||||||
|
t.Logf("Setenv(%q, %q)", k, v)
|
||||||
|
} else {
|
||||||
|
t.Errorf("Environ contains %q, but SetEnv(%q, %q) = %q", kv, k, v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
56
src/os/env_unix_test.go
Обычный файл
56
src/os/env_unix_test.go
Обычный файл
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//+build darwin linux
|
||||||
|
|
||||||
|
package os_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
. "os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var setenvEinvalTests = []struct {
|
||||||
|
k, v string
|
||||||
|
}{
|
||||||
|
{"", ""}, // empty key
|
||||||
|
{"k=v", ""}, // '=' in key
|
||||||
|
{"\x00", ""}, // '\x00' in key
|
||||||
|
{"k", "\x00"}, // '\x00' in value
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetenvUnixEinval(t *testing.T) {
|
||||||
|
for _, tt := range setenvEinvalTests {
|
||||||
|
err := Setenv(tt.k, tt.v)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf(`Setenv(%q, %q) == nil, want error`, tt.k, tt.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shellSpecialVarTests = []struct {
|
||||||
|
k, v string
|
||||||
|
}{
|
||||||
|
{"*", "asterisk"},
|
||||||
|
{"#", "pound"},
|
||||||
|
{"$", "dollar"},
|
||||||
|
{"@", "at"},
|
||||||
|
{"!", "exclamation mark"},
|
||||||
|
{"?", "question mark"},
|
||||||
|
{"-", "dash"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpandEnvShellSpecialVar(t *testing.T) {
|
||||||
|
for _, tt := range shellSpecialVarTests {
|
||||||
|
Setenv(tt.k, tt.v)
|
||||||
|
defer Unsetenv(tt.k)
|
||||||
|
|
||||||
|
argRaw := fmt.Sprintf("$%s", tt.k)
|
||||||
|
argWithBrace := fmt.Sprintf("${%s}", tt.k)
|
||||||
|
if gotRaw, gotBrace := ExpandEnv(argRaw), ExpandEnv(argWithBrace); gotRaw != gotBrace {
|
||||||
|
t.Errorf("ExpandEnv(%q) = %q, ExpandEnv(%q) = %q; expect them to be equal", argRaw, gotRaw, argWithBrace, gotBrace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,6 +147,19 @@ func Getenv(key string) (value string, found bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Setenv(key, val string) (err error) {
|
func Setenv(key, val string) (err error) {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return EINVAL
|
||||||
|
}
|
||||||
|
for i := 0; i < len(key); i++ {
|
||||||
|
if key[i] == '=' || key[i] == 0 {
|
||||||
|
return EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < len(val); i++ {
|
||||||
|
if val[i] == 0 {
|
||||||
|
return EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
keydata := cstring(key)
|
keydata := cstring(key)
|
||||||
valdata := cstring(val)
|
valdata := cstring(val)
|
||||||
errCode := libc_setenv(&keydata[0], &valdata[0], 1)
|
errCode := libc_setenv(&keydata[0], &valdata[0], 1)
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче