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) | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Dan Kegel
						Dan Kegel