Repository navigation
proposal: os: add EnvironMap function #75934
Description
Activity
Related Issues
- proposal: os: make a Go 2 type for environment variables? #25252
- os: Getenv documentation update to point user to syscall.Getenv #9676 (closed)
- proposal: os: Splitenv #62038
- x/sys/plan9, x/sys/windows: Environment functions should just refer to syscall functions #10803 (closed)
- os/exec: add Cmd.SetEnv method #12868 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
- addedLibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolIssues describing a requested change to the Go standard library or x/ libraries, but not to a tool
on Oct 16, 2025 syscall is frozen, see #62038
Reopening this as a proposal for the os package.
@lbordowitz The best way to argue for this would be to show examples in third party packages that would be improved with such a function. We would only add this feature if there is reasonably widespread use.
- changed the title
[-]proposal: syscall: EnvironMap[/-][+]proposal: os: add EnvironMap function[/+]on Oct 16, 2025 @ianlancetaylor Use cases in repositories with over 10,000 stars:
- In Kubernetes
- In frp, the fast reverse proxy
- In etcd (Used for getting env var names)
- In ethereum Used for getting env var names)
- In OpenTofu (Hence, also probably Terraform)
- In Istio
- In Tailscale
- In Pulumi
- In yay (for filtering env var names)
- In Open Policy Agent
While searching repositories and code, I found it interesting how
Environis used instead ofGetenv, and the code then searches for a prefix in the env var names.I note that in several of those examples there's a filtering step where only a subset of the items from
Environget included in the result. If those cases were rewritten to use the proposedEnvironMapthen they would presumably first generate a full map and then either only use a subset of it or delete elements from it before returning.That makes me consider a different compromise:
package os import ( "iter" ) // EnvironPairs returns a sequence of each of the elements that would // be returned by [Environ], split into separate name/value pairs. // // Any elements of the [Environ] result that do not include "=" as a // delimiter between name and value are skipped. func EnvironPairs() iter.Seq2[string, string]
A caller that wants a map without any special filtering could then use
maps.Collectwith the result of this function to collect the pairs into a map as a separate step:maps.Collect(os.EnvironPairs()). The examples that want to construct a slice of some struct type instead of a map can also use this sequence result to do that without allocating an intermediate map. The examples that want to filter can write afor ... rangeloop over theos.EnvironPairsresult that includes anifstatement.My intention in ignoring elements that don't represent conventional name/value pairs was that
maps.Collect(os.EnvironPairs())[anyName]should be equivalent toos.Getenv(anyName)and so the resulting map should not include anything thatos.Getenvwould not be able to match. However, to properly create that equivalence,EnvironPairswould need to iterateEnvironin reverse order so that the earlier elements would be returned last so that duplicate names would have the same priority as foros.Getenv, which is admittedly quite weird for any use other than collecting into a map. Maybe it would be better to prioritize the non-map-collecting use-cases and assume that folks who want to access just a single variable by name would continue usingos.Getenv.(I don't feel strongly about the name
EnvironPairs, but I don't know what else to call it.)Reacted by Carlana, Larry Bordowitz and Juan Felipe AlvarezWhat's the actual proposed functions here?
Your search only shows that it's common to loop over os.Environ, but not that any particular output format structure is shared between them.If you search for strings.Cut/strings.SplitN on =, you can find this lots of places including x/tools, but it's frequently working with a derivative of os.Environ, not os.Environ itself:
- https://github.com/golang/tools/blob/c2e79795ae0d753340da9a7b0060f44314f455e5/go/packages/golist.go#L1028-L1033
- https://github.com/golang/tools/blob/c2e79795ae0d753340da9a7b0060f44314f455e5/gopls/internal/cache/session.go#L212
- https://github.com/golang/tools/blob/c2e79795ae0d753340da9a7b0060f44314f455e5/gopls/internal/test/integration/options.go#L185-L190
- https://github.com/golang/tools/blob/c2e79795ae0d753340da9a7b0060f44314f455e5/internal/imports/fix_test.go#L1703
Maybe it should be
os.PairFromEnviron([]string) iter.Seq2[string, string].Maybe a better name is
os.KeysValuesFromEnviron.What's the actual proposed functions here? Your search only shows that it's common to loop over os.Environ, but not that any particular output format structure is shared between them.
The proposed functionality is to split environment variables into name and value. That is what's being done in every one of these examples. I suggested that the output might be a map, where a developer could filter on keys, get a subset of values, etc. I think Martin's suggestion, generating a list of string pairs, is more efficient.
My point is, whenever
Environisn't being shuttled directly into another process, it is being chopped up in a very common way.Reacted by CarlanaContinuing down the path that @earthboundkid started above, I guess the smallest possible addition here would be a function that takes one string in the format used by
os.Environand returns(name, value string)after parsing it. That exposes the smallest part that other utilities, like constructing a map or implementing aniter.Seq2, could then be built around.Though in practice I don't know if there's any use-case for parsing just one environment string in isolation, vs. parsing all that are present in some sequence of them like what
os.Environandexec.Cmd.Environreturn, so I think a form that returns a sequence (as we've been discussing) is a nice sweet spot for the examples we've been looking at so far.Part of me wants to suggest that it should take
iter.Seq[string]instead of[]stringas its argument, for symmetry with the return type, but that does mean that the (presumed) common case of working with a slice would involve usingslices.Values, which is a little annoying. 🤷♂️
Edit: Thanks to comments below I was reminded that the "function that takes one string" was already previously proposed in #62038, which remains undecided at the time of my updating this comment.
It seems to me that this proposal is essentially just a variant of the other one which works with the entire
Environslice at once, rather than only one entry from it.Reacted by CarlanaI think we're still lacking an actual proposed function?
- addedWaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.Issue is not actionable because of missing required information, which needs to be provided.
on Nov 8, 2025 To summarize, one proposal is for a function (proposed by @apparentlymart in this comment that I would describe as a "pair function":
// EnvironPairs returns a sequence of each of the elements that would // be returned by [Environ], split into separate name/value pairs. // // Any elements of the [Environ] result that do not include "=" as a // delimiter between name and value are skipped. func EnvironPairs() iter.Seq2[string, string]
I would say that the first function signature proposal, for a "pair function", is the most conducive to filtering and later usages, such as are being done in the linked open source projects. I would prefer this one the most, but I will list the others for the sake of completeness.
Another proposal (made by @earthboundkid here) is for a function that parses the existing Environ into those pairs, perhaps calling it a "parsing function". So perhaps the signature would look like this:
func EnvironPairs(environ []string) iter.Seq2[string, string]
I'll mention that parsing the existing environment comes up a lot in the comments of this propsal. The only downside is that the input is less controlled. So, if a listing in the
environ []stringdoes not conform to thename=valuepattern, the functionality is undefined.The original proposal was for a "map function", which would go from environment name to corresponding value:
func EnvironMap() map[string]string
Any benefit this proposal has is probably superseded by the "pair function" in terms of simplicity.
- removedWaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.Issue is not actionable because of missing required information, which needs to be provided.
on Nov 12, 2025
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Proposal Details
There are two primary use-cases for the
os.Environfunction:The latter requires going through each string in
os.Environ(), usually splitting it on the=sign, and then getting the last part. This is perilous and potentially bug-prone: what if there's an"="character in the value of the environment variable? Re-joining everything ""? It is not efficient. A widespread need with so many different implementations recommends a library standard function to cut through the noise.The parsing method in the unix
syscall.Getenvcould be abstracted to provide a methodEnvironMap() map[string]stringfor the entirety of theenvs, and the windows syscallGetEnvironmentStringsWcan be similarly parsed. I recommend adding this method in thesyscallpackage, and then exposing it inosas well.