PowerShell needs shortcut vars for [IO.Path]::PathSeparator and [IO.Path]::DirectorySeparatorChar

Created on 4 Nov 2017  路  15Comments  路  Source: PowerShell/PowerShell

I'm sorry but having to drop down to the .NET Framework to access the characters needed to separate paths and the appropriate slash to use to separate directories in a path needs to be built-in and more accessible to non-programmer scripters (admins).

I mean sort of the whole point of PS Core is to be cross-platform. Seems like we should grease the skips for this set of scenarios.

Steps to reproduce

You need to append a path to $env:PATH in a cross platform way. As an admin scripter, how do you do that?

Expected behavior

Admin scripter should be able to find a "built-in" variable in the about_automatic_variables and then use the variable like so:

$env:Path += "${PSPathSep}${myNewPath}"
# Or they want a simple string literal join of path segments
$exePath = "${InstallDir}${PSDirSep}foo.exe"

Actual behavior

Admin scripter has to go look up some obtuse "programmy" syntax:

$env:Path += "$([IO.Path]::PathSeparator)${myNewPath}"
$exePath = "${InstallDir}$([IO.Path]::DirectorySeparatorChar)foo.exe"

We can do better than this.

Environment data

PS Core 6

Issue-Discussion

Most helpful comment

I like the idea of supporting += on "Path" like variables - that's worth some more thinking.

Today, an environment variable is a string, but if we could have strongly typed environment variables, we could implement operators like + and [].

All 15 comments

I'm not excited about adding another $PS variable because the prefix is ugly, but an obvious variable is likely to collide with a user variable.

Join-Path is what non-developers use for directories - it could take a parameter like uh, well, not -Path, but uh, hmm :) PathSeparator is poorly named I guess.

Yeah, in retrospect maybe if would have been nicer if this was a bit more "encapsulated" e.g. $PS:PathSep and $PS:DirSep. So rather than a litter of global variables, there is a single PS variable drive. For comparison, Python uses os.pathsep and os.sep for these.

@rkeithhill:

it would have been nicer if this was a bit more "encapsulated" e.g. $PS:PathSep and $PS:DirSep

Indeed; d茅j脿 vu: https://github.com/PowerShell/PowerShell/issues/4394#issuecomment-321955860

@lzybkr: Join-Path doesn't really need a new parameter, because it already uses the platform-appropriate separator - and extending its purpose to also append to $env:PATH seems like a poor fit.


For the compose-a-single-filesystem-path scenario, Join-Path is therefore sufficient (especially now that it accepts a variable number of components):

$exePath = Join-Path $InstallDir foo.exe

That said, having something like $ps:DirSep too can't hurt.
(Arguably, it should be $ps:PathSeparator - you're not just separating _directories_ but _path components_, which can be directories or _files_; it's hard to say whether following .NET's misguided nomenclature is more important than getting it right.)


As for the adding-an-entry-to-$env:PATH scenario:

Indeed, [IO.Path]::PathSeparator is poorly named.

It's been suggested before (I forget where) that PowerShell provide an _array_-based way of accessing and modifying $env:PATH, which would hide the platform-specific _PATH-environment-variable-entry-separator_ (; on Windows, : on Unix).

# Wishful thinking: treat $env:PATH as an array
$ps:Path += $myNewPath

Again, also having something like $ps:PathVarSep can't hurt.

I like the idea of supporting += on "Path" like variables - that's worth some more thinking.

Today, an environment variable is a string, but if we could have strongly typed environment variables, we could implement operators like + and [].

I would discourage using path separator variables because it creates a bad coupling when concatenating paths since one has to know if the path already ends with a path separator or not to avoid having 2 path separators or none. Join-Path or [System.IO.Path]::Combine takes care of all that. And even if you used the 'wrong' slashes, all .NET core methods are agnostic of it. Should you have some other API that is picky, then just pass your path string to Get-Item and PowerShell will correct your slashes.

You're referring to the directory separator and yeah Join-Path is a reasonable alternative. Path separator ; or : is not handled by Join-Path or the other .NET methods you mention - not that an admin should have to resort to using .NET methods for such a simple, common scenario.

OK, I see what you mean now in the case of path separators. Sorry about my loose use of vocabulary.

No problem. As some folks have pointed out already, the naming leaves something to be desired.

I bumped into this under PowerShell Core for prepending to $env:PATH. I ended up using ($binPath, $env:PATH) -join [IO.Path]::PathSeparator but I only knew that thanks to a friend who knows PowerShell and C#. 馃槃

Wouldn't it be better to have a function?

I mean, obviously you don't want to append your path to the string if it's already there (right?), so a function that can join path variables automatically using the right character for the _and_ preventing duplicates with knowledge of the OS case-sensitivity would be really helpful...

How about a -Separator parameter to Join-Path that defaults to Directory, but an enum that also has Path that maps to [IO.Path]::PathSeparator?

$env:PATH = Join-Path $env:PATH $binPath -Separator Path

I like the idea. I don't particularly like the chosen names there... there's not enough semantic distance between the two, I think.

Besides, this would still require manual handling of duplicate entries in the path -- I think @Jaykul's proposal for a cmdlet/function that handles this stuff consistently in a neat package would be ideal, and would help integrate PowerShell into the different operating systems more smoothly. 馃槃

I don't think this functionality should be tacked onto Join-Path, which is about _composing a (set of) path(s)_ - something quite distinct from _managing the entries of an environment variable containing a list of paths_.

As @Jaykul and @vexx32 suggest, having a dedicated cmdlet to manage such variables makes sense, and if that cmdlet covers all desired modification scenarios to such a variable, the need to expose the platform-specific separator directly goes away.

_Update_: See https://github.com/PowerShell/PowerShell-RFC/pull/92 for a preexisting discussion on this.

That is, the following 3 operations are needed when it comes to environment variables such as $env:PATH- but note that the same logic is applicable to similar env. variables such as $env:CLASSPATH:

  • Append a new directory path.

  • Prepend a new directory path.

  • Remove a directory path.

As suggested, in the case of appending or prepending, it makes sense to avoid duplicate entries and by default do nothing, if the target directory is already present.
This can be supplemented with -Force to ensure moving an existing entry to the beginning or end.

Here's what the syntax diagram for such a cmdlet could look like (name negotiable):

Update-PathEnvironmentVariable `
  [[-Name]=<string>='PATH'] [-Remove <string[]>] [-Prepend <string[]>] [-Append <string[]>] `
  [-Target = 'Process'|'User'|'Machine'] `
  [-Force]

The default -Target would be Process, i.e., updating the variable for the current process only.

Targeting User or Machine to effect _persistent_ env.-var. changes would work on _Windows only_, because the underlying .NET method that would be used - Environment.SetEnvironmentVariable - doesn't support this on macOS and Linux.
In fact, it _quietly ignores_ these values- see https://github.com/dotnet/corefx/issues/32685

having a dedicated cmdlet to manage such variables makes sense

Um, like this? https://github.com/PowerShell/PowerShell-RFC/pull/92

Thanks, @rkeithhill - I'd lost track of that RFC early on; good to see all the discussion there.

Just briefly (I'll take the discussion over to the RFC):

  • ~I now think that a single, generic list-string manipulation is called for that doesn't itself set environment variables, but allows manipulation of their _values_, if they're list-based, with an -AsDirList switch to use the platform-abstracted [IO.Path]::PathSeparator - see #7975~

  • Conversely, that would mean keeping any list-parsing functionality out of any *-Environment cmdlets and, more fundamentally, I think there shouldn't even be new *-Environment cmdlets - instead, the existing environment-variable drive provider should be enhanced to support persistence.

Even though with the above the need to surface [IO.Path]::PathSeparator explicitly in a PowerShell-friendly manner is diminished, I do still see value in _also_ doing that, and, as stated before, I'm with you on surfacing such variables via a new namespace / drive, as discussed in https://github.com/PowerShell/PowerShell/issues/4394#issuecomment-321955860 - TBD whether with a catch-all drive such as $ps:... or - perhaps taking a page out of Python's book - grouped thematically so that $os:... could contain $os.PathVariableSeparator ([IO.Path]::PathSeparator) as well as $os.DirectorySeparator ([IO.Path]::DirectorySeparatorChar), and possibly more (names negotiable, but I don't think CoreFx's confusing nomenclature is worth keeping in this instance).

Was this page helpful?
0 / 5 - 0 ratings