Skip to content

Bug fixes needed to support interchangeable use of "/" (forward slash) and "\" (backslash) in file paths on Unix-like platforms #9244

@mklement0

Description

@mklement0

Note:


From the committee decision at #8587 (comment):

Filesystem accepting both / and \ on all systems is an explicit cross platform experience decision for PowerShell.

Given that it's currently impossible on Unix-like platforms - where \ is a legal filename character - to access or wildcard-match filenames with embedded \ chars., the following fixes are needed:

  • Wildcard matching must able to match \ chars., namely via ? or * or [\].

    • Note that inside [...], \ shouldn't require escaping, analogous to [.] matching a literal . in a regex.
  • -Path and -LiteralPath arguments must support `-escaping of \ chars. to request their literal treatment.

  • The .PS* provider property values (.PSPath, .PSParentPath, .PSChildName) must reflect the escaped path.

  • To-native-path conversions such as with Convert-Path must remove the escaping.

  • Native globbing must not break if a wildcard pattern happens to match a filename that contains \.

Note:

  • Even the above doesn't address the awkwardness of the - unescaped - .FullName value of System.IO.FileSystemInfo not being directly usable with the filesystem provider; e.g., with the provider bug fixed, something like (Get-ChildItem "$HOME/a\b").FullName would return something like /Users/jdoe/a\b - unescaped.

  • Similarly, native paths from outside sources with embedded \ - e.g., read from a file - will break in PowerShell commands, unless explicit escaping with -replace '`', '\`' is performed.

  • ` as the escape char. means that it requires doubling inside "..." and therefore also in unquoted arguments in order to be passed through; i.e., the following calls would be equivalent and match a file literally named a\b:

Get-Item 'a`\b'
Get-Item "a``\b" # !! 
Get-Item a``\b # !! implicitly treated like a "..." string
  • Handling ` instances that precede a char. other than \ gets quite tricky:

    • Literal paths: So as not to break the existing ability to match ` literally in paths, ` must be special only before \ and before ` itself. This follows the model of \ use in double-quoted strings in Bash, but introduces the awkwardness of needing to interpret 'a`b' and 'ab' `` the same (similar to how "a\b" and `"a\b"` are equal in Bash).

    • Wildcard paths: Wildcard matching itself always requires doubling of ` for literal use; e.g.,
      'a`b' -like 'a`b' is $false, and only 'a`b' -like 'a``b' is $true (edge case: 'a`' -like 'a`' is also $true - the trailing syntactically incomplete use of ` is interpreted as a literal).

      • However, use of wildcards with the filesystem provider:
        • currently makes Get-Item -Path 'a`b' match a file literally named a`b, even though it shouldn't.
        • currently doesn't find a match with Get-Item -Path 'ab' and neither with Get-Item -Path 'a?' , even though it should.
      • In other words: wildcard handling by the filesystem provider is currently broken - this may be related to Backtick escaping inconsistent #7999.
      • While falling back to literal interpretation is perhaps useful, especially given that -Path is the default parameter, this should be fixed, separately.

Steps to reproduce

On macOS or Linux, run the following Pester tests:

# Note: -Skip isn't yet supported at the level of Describe and Context blocks.
#        See https://github.com/pester/Pester/issues/442
if ($env:OS -eq 'Windows_NT') {
  Write-Warning "Tests apply to Unix-like platforms only" }
else {
  Describe "Support for backslashes in Unix filenames" {
    BeforeAll  {
      Push-Location TestDrive:\
      # File with literal backslash in its name.
      touch 'a\b'
      $nativePath = ($PWD.ProviderPath -replace '/$') + '/' + 'a\b'
      $escapedName = 'a`\b'
      $escapedPath = ((Get-Item .).PSPath -replace '/$') + '/' + $escapedName
      # File with literal backtick in its name.
      touch 'a`b'
    }
    It "Backslashes can be matched with wildcards" {
      (Get-Item -Path 'a?b').Name   | Should -BeExactly 'a\b'
      (Get-Item -Path 'a*b').Name   | Should -BeExactly 'a\b'
      (Get-Item -Path 'a[\]b').Name | Should -BeExactly 'a\b'
    }
    It "Backtick-escaped backslashes match literal ones in filenames" {
      (Get-Item -Path 'a`\b').Name | Should -BeExactly 'a\b'
      (Get-Item -LiteralPath 'a`\b').Name | Should -BeExactly 'a\b'
    }
    It "Backticks not preceding a backslash or backtick are literals in literal paths" {
      (Get-Item -LiteralPath 'a`b').Name | Should -BeExactly 'a`b'
    }
    It "Doubled backticks are also literals in literal paths" {
      (Get-Item -LiteralPath 'a``b').Name | Should -BeExactly 'a`b'
    }
    It "Backtick-escaped backticks must be recognized as a literal backtick in wildcard paths" {
      (Get-Item -Path 'a``?').Name | Should -BeExactly 'a`b'
    }
    It "Provider properties contain escaped paths" {
      (Get-Item -LiteralPath 'a`\b').PSChildName | Should -BeExactly $escapedName
      (Get-Item -LiteralPath 'a`\b').PSPath | Should -BeExactly $escapedPath
    }
    It "Convert-Path removes the PS-specific escaping" {
      Get-Item -LiteralPath 'a`\b' | Convert-Path | Should -BeExactly $nativePath
    }
    It "Native globbing doesn't break when a filename with a backslash is among the matches" {
      /bin/echo a* | Should -Not -Match '\*'
    }
    AfterAll {
      Pop-Location
    }
  }

}

Expected behavior

All tests should pass.

Actual behavior

All tests except "Backticks not preceding a backslash or backtick are literals in literal paths" fail.

Environment data

PowerShell Core 6.2.0-rc.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-FileSystem-Providerspecific to the FileSystem providerIssue-BugIssue has been identified as a bug in the productResolution-No ActivityIssue has had no activity for 6 months or moreWG-Engine-Providersbuilt-in PowerShell providers such as FileSystem, Certificates, Registry, etc.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions