Skip to content

proposal: os: add EnvironMap function #75934

Description

@lbordowitz

Proposal Details

There are two primary use-cases for the os.Environ function:

  1. Passing through environment variables to a sub-command or other executable
  2. Parsing all environment variables into a map

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.Getenv could be abstracted to provide a method EnvironMap() map[string]string for the entirety of the envs, and the windows syscall GetEnvironmentStringsW can be similarly parsed. I recommend adding this method in the syscall package, and then exposing it in os as well.

Activity

  1. added this to the Proposal milestone on Oct 16, 2025
  2. added
    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a tool
    on Oct 16, 2025
  3. seankhliao commented on Oct 16, 2025

    @seankhliao
    Member

    syscall is frozen, see #62038

  4. ianlancetaylor commented on Oct 16, 2025

    @ianlancetaylor
    Contributor

    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.

  5. changed the title [-]proposal: syscall: EnvironMap[/-] [+]proposal: os: add EnvironMap function[/+] on Oct 16, 2025
  6. lbordowitz commented on Oct 17, 2025

    @lbordowitz
    Author

    @ianlancetaylor Use cases in repositories with over 10,000 stars:

    While searching repositories and code, I found it interesting how Environ is used instead of Getenv, and the code then searches for a prefix in the env var names.

  7. apparentlymart commented on Oct 17, 2025

    @apparentlymart

    I note that in several of those examples there's a filtering step where only a subset of the items from Environ get included in the result. If those cases were rewritten to use the proposed EnvironMap then 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.Collect with 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 a for ... range loop over the os.EnvironPairs result that includes an if statement.

    My intention in ignoring elements that don't represent conventional name/value pairs was that maps.Collect(os.EnvironPairs())[anyName] should be equivalent to os.Getenv(anyName) and so the resulting map should not include anything that os.Getenv would not be able to match. However, to properly create that equivalence, EnvironPairs would need to iterate Environ in reverse order so that the earlier elements would be returned last so that duplicate names would have the same priority as for os.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 using os.Getenv.

    (I don't feel strongly about the name EnvironPairs, but I don't know what else to call it.)

  8. seankhliao commented on Oct 18, 2025

    @seankhliao
    Member

    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.

  9. earthboundkid commented on Oct 20, 2025

    @earthboundkid
    Contributor

    Maybe a better name is os.KeysValuesFromEnviron.

  10. lbordowitz commented on Oct 20, 2025

    @lbordowitz
    Author

    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 Environ isn't being shuttled directly into another process, it is being chopped up in a very common way.

  11. apparentlymart commented on Oct 20, 2025

    @apparentlymart

    Continuing 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.Environ and returns (name, value string) after parsing it. That exposes the smallest part that other utilities, like constructing a map or implementing an iter.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.Environ and exec.Cmd.Environ return, 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 []string as its argument, for symmetry with the return type, but that does mean that the (presumed) common case of working with a slice would involve using slices.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 Environ slice at once, rather than only one entry from it.

  12. seankhliao commented on Nov 8, 2025

    @seankhliao
    Member

    I think we're still lacking an actual proposed function?

  13. added
    WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.
    on Nov 8, 2025
  14. lbordowitz commented on Nov 12, 2025

    @lbordowitz
    Author

    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 []string does not conform to the name=value pattern, 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.

  15. removed
    WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.
    on Nov 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    • Status
      No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions