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 | ||||
| 
 | ||||
| import ( | ||||
| 	"internal/testlog" | ||||
| 	"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 { | ||||
| 	testlog.Getenv(key) | ||||
| 	v, _ := syscall.Getenv(key) | ||||
| 	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 { | ||||
| 	err := syscall.Setenv(key, value) | ||||
| 	if err != nil { | ||||
|  | @ -17,12 +124,9 @@ func Setenv(key, value string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Unsetenv unsets a single environment variable. | ||||
| func Unsetenv(key string) error { | ||||
| 	err := syscall.Unsetenv(key) | ||||
| 	if err != nil { | ||||
| 		return NewSyscallError("unsetenv", err) | ||||
| 	} | ||||
| 	return nil | ||||
| 	return syscall.Unsetenv(key) | ||||
| } | ||||
| 
 | ||||
| // Clearenv deletes all environment variables. | ||||
|  | @ -30,10 +134,8 @@ func Clearenv() { | |||
| 	syscall.Clearenv() | ||||
| } | ||||
| 
 | ||||
| func LookupEnv(key string) (string, bool) { | ||||
| 	return syscall.Getenv(key) | ||||
| } | ||||
| 
 | ||||
| // Environ returns a copy of strings representing the environment, | ||||
| // in the form "key=value". | ||||
| func Environ() []string { | ||||
| 	return syscall.Environ() | ||||
| } | ||||
|  |  | |||
|  | @ -11,6 +11,82 @@ import ( | |||
| 	"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) { | ||||
| 	e0 := Environ() | ||||
| 	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") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 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) { | ||||
| 	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) | ||||
| 	valdata := cstring(val) | ||||
| 	errCode := libc_setenv(&keydata[0], &valdata[0], 1) | ||||
|  |  | |||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Dan Kegel
						Dan Kegel