Skip to content

Parameter values disambiguated with their explicit parameter names should not require quoting #4576

Closed

Description

In order to pass a parameter value that looks like a parameter name you currently have to use quoting - sometimes.

However, if you explicitly precede the value with its parameter name as opposed to passing it positionally, no quoting should be necessary.

Especially with respect to PowerShell's CLI and all its quoting headaches, this extra quoting requirement should be reconsidered - see #4351

Steps to reproduce

'hi'>'-t.txt'; Get-Content '-t.txt'; Get-Content -LiteralPath -t.txt

Expected behavior

hi
hi

The two Get-Content commands should be equivalent:

  • In the 1st case, the quoting implies that -t.txt is a parameter value rather than a name.
  • In the 2nd case, the explicit use of parameter -LiteralPath, which requires a value, should unambiguously identify the next token as the parameter value - even if it starts with - - whether quoted or not.

Actual behavior

hi
Get-Content : Parameter cannot be processed because the parameter name 't' is ambiguous. Possible matches include: -TotalCount -Tail.
...

That is, despite being preceded by -LiteralPath, which requires a parameter value,
unquoted -t.txt was not interpreted as that parameter's value, but as another parameter name.

Environment data

PowerShell Core v6.0.0-beta.5 on macOS 10.12.6
PowerShell Core v6.0.0-beta.5 on Ubuntu 16.04.3 LTS
PowerShell Core v6.0.0-beta.5 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Windows PowerShell v5.1.15063.483 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)

Activity

  1. added
    WG-Languageparser, language semantics
    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassif
    on Aug 15, 2017
  2. markekraus commented on Aug 15, 2017

    @markekraus
    Contributor

    I'll weight in that this is not a good idea, IMO.

    For example:

    Get-Command -Module -Name Get-Help

    With your proposal in place, looking at that command should it be interpreted as

    Get-Command -Module '-Name' -Name 'Get-Help'

    or

    Get-Command  -Name 'Get-Help' -Module # Missing an argument for parameter 'Module'...

    This could cause unexpected/unpredictable behavior and lead to a bad UX. This also opens a Pandora's box of exceptions like passing string parameters with subexpression operators. Strings that contain special tokens should be quoted while strings without special token can be passed without quoting as a kind of convenient syntactic sugar. Unquoted string parameters should be the exception, not the rule, IMO.

  3. mklement0 commented on Aug 15, 2017

    @mklement0
    ContributorAuthor

    Mark Kraus (@markekraus):

    I get your concern, but do note:

    • that there's no syntactical ambiguity: in PowerShell, a parameter either requires a value or is a switch; so, for any given parameter name specified explicitly, it is unambiguously implied whether the next token is a parameter value or a different parameter.

    • that even single-character - suffixes - where there's no obvious resemblance to a parameter name - can trigger the problem; e.g., -n.

    • that the current behavior is inconsistent:

    > Write-Output -InputObject -z
    -z
    
    > Write-Output -InputObject -n
    Write-Output : Missing an argument for parameter 'InputObject'. Specify a parameter of type 'System.Management.Automation.PSObject[]' and try again.

    As you can see, -z, because it happens not to be the start of a parameter name, is accepted as -InputObject's parameter value.

    By contrast, because -n happens to be the start of -NoEnumerate, it is interpreted as that, and then -InputObject has no value, and the command breaks.


    I suppose that one way to resolve the problem would be to disallow unquoted values that start with -, but my concern would then be PowerShell's CLI, where introducing such an additional quoting requirement (e.g., the need to pass a script filename -t.ps1 explicitly quoted) would further aggravate the quoting headaches, e.g., when calling from bash:

    # Currently works.
    powershell -noprofile -file -t.ps1
    
    # Here's what the need to quote explicitly would entail:
    # *2* layers of quoting: an outer layer interpreted by Bash that gets removed before
    # PS is passed the value, and the inner layer for PowerShell's benefit.
    powershell -noprofile -file "'-t.ps1'"

    I guess the CLI could continue to operate differently, but such inconsistency is ill-advised.

  4. markekraus commented on Aug 15, 2017

    @markekraus
    Contributor

    that there's no syntactical ambiguity: in PowerShell, a parameter either requires a value or is a switch;

    Yes and I'm suggesting this would introduce that ambiguity. In my example you could consider Get-Help the positional parameter for -Name and -Name as the string parameter for -Module. That's not what I as a user would expect. I would expect to be told I forgot to provide a value for -Module. Instead, I would just get nothing in return assuming I don't have a Get-Help command in a -Name module.

    I suppose that one way to resolve the problem would be to disallow unquoted values that start with -

    If any change is made, I would say that this one is the way to go. I didn't even know - could begin any unquoted string value.

    As for the rest, I'm not sure that's the case unless i misunderstand you. It appears you are seeking consistency between parent shells and PowerShell syntax. Trying to make the PowerShell CLI consistent with other Shells is a bit futile.

    When PowerShell is the parent shell, you already have to quote -t.ps1

    PS C:\> powershell -noprofile -file -t.ps1
    Processing -File '-t' failed because the file does not have a '.ps1' extension. Specify a valid Windows PowerShell script file name, and then try again.
    
    PS C:\> powershell -noprofile -file '-t.ps1'
    # Executes the contents of -t.ps1
    

    I wouldn't expect PowerShell's argument parsing to be consistent with Bash, CMD, ksh, python REPL, or other interactive CLI.

  5. mklement0 commented on Aug 15, 2017

    @mklement0
    ContributorAuthor

    Yes and I'm suggesting this would introduce that ambiguity.

    There's no ambiguity, because what I'm proposing is state-dependent, based on the specific syntax of the command in question:

    • With a clean slate, -Name is unambiguously the parameter name.
    • Once that has been recognized, given that the -Name parameter requires a value, the next token is unambiguously the parameter value - even if it looks like a parameter.

    It's not that common a pattern in POSIX-like shells, but the equivalent is an option with a mandatory option-argument; for instance, if you want to pass a script filename to sed via its -f option, the next token is invariably treated as the filename, even if it starts with -:

    sed -f -my_script ...  # -my-script is invariably treated as the option *argument*

    Again, in the case of Get-Command -Module -Name Get-Help it is obvious what the intent is, but let's not forget that relative to the intent it is a syntax error - based on a misunderstanding of how the -Module parameter works.

    In other cases, such as in my -n example, the intent may be quite different, and since -InputObject -n is a syntactically unambiguous way to pass -n as a value - because -InputObject requires one - it should be treated as such.


    PS C:\> powershell -noprofile -file -t.ps1
    Processing -File '-t' failed because the file does not have a '.ps1' extension. Specify a valid Windows PowerShell script file name, and then try again.

    That this fails from inside PowerShell looks like a bug.


    I wouldn't expect PowerShell's argument parsing to be consistent with Bash, CMD, ksh, python REPL, or other interactive CLI.

    I didn't mean other shells, I meant the consistency between PowerShell's CLI and parameter passing to cmdlets and functions:

    If:

    • powershell -noprofile -file -t.ps1 works (which it should and currently does from cmd.exe and bash and, as stated, that it doesn't from inside PowerShell should be considered a bug),

    • something like Get-Content -LiteralPath -t.ps1 should work too.

  6. markekraus commented on Aug 15, 2017

    @markekraus
    Contributor

    that it doesn't from inside PowerShell should be considered a bug

    I don't think so. It should be considered a difference of syntax between shells. How a shell parses arguments to pass to binary is up to that shell and there are only similarities between shells. PowerShell as a shell will have a different method for parsing and passing arguments to binaries then Bash, CMD, etc. that would be expected. What one shell deems an important token another may deem irrelevant.

    Internally, PowerShell is consistent:

    #Doesn't work
    powershell -noprofile -file -t.ps1
    Get-Content -LiteralPath -t.ps1
    
    # Works
    powershell -noprofile -file '-t.ps1'
    Get-Content -LiteralPath '-t.ps1'
  7. mklement0 commented on Aug 15, 2017

    @mklement0
    ContributorAuthor

    The way in which it fails tells us that something is amiss - the two commands fail for different reasons:

    • Get-Content -LiteralPath -t.ps1 fails, because -t.ps1 is interpreted as a parameter name (the starting point of our discussion - purely syntactically, given that --prefixed barewords are allowed, this should work).

    • powershell -noprofile -file -t.ps1 fails due to an unrelated bug, because, even though -t.ps1 is interpreted as a parameter value in principle, it unexpectedly considers the .ps1 suffix a separate argument, and -file only sees filename -t, not -t.ps1

    The latter, as I've just discovered, is a generic parsing bug:

    (At least I see no reason why, if --prefixed tokens can be barewords in principle, a .-prefixed suffix should cause the bareword to be split into 2 arguments.)

    # OK
    > Write-Output -InputObject -t
    -t
    
    # BROKEN
    > Write-Output -InputObject -t.ps1
    Write-Output : A positional parameter cannot be found that accepts argument '.ps1'.

    Essentially, PS parses Write-Output -InputObject -t.ps1 as if .ps1 had been passed as a separate argument - Write-Output -InputObject -t .ps1 - even though I see no good reason for that - Write-Output -InputObject t.ps1 (value doesn't start with -) works just fine, for instance.

  8. markekraus commented on Aug 15, 2017

    @markekraus
    Contributor

    Huh.. to add to the weirdness... this is totally possible...

    $Variable = 'temp'
    Write-Output -t*+_*^%$variable#@!<>/?\]=+-
    Write-Output -InputObject -t*+_*^%$variable#@!<>/?\]=+-
    #output: for both:
    -t*+_*^%$variable#@!<>/?\]=+-

    (,),',", [,{,} and . all break it, but any of the others, including what would normally be a variable, is treated as a literal string.

    I really think the solution is to treat - as a reserved prefix for named parameters. I would expect

    Write-Output -NonexistentParameter

    to throw a NamedParameterNotFound exception, and I would expect

    Write-Output -InputObject -NonexistentParameter

    To throw a MissingArgument exception. I definitely would not expect either to output -NonexistentParameter as a string.

  9. mklement0 commented on Aug 15, 2017

    @mklement0
    ContributorAuthor

    Good find re lack of expansion.

    I've created #4578 to focus on the aspect of --prefixed bareword parsing (and to generally ask the question whether it should be supported).

    I'm with you with respect to Write-Output -NonexistentParameter: without a preceding, valid parameter name that requires a value, -NonexistentParameter should be parsed as a parameter name (and in order to force interpretation as a positional value the user would have to quoting; e.g.,
    '-NonexistentParam' or `-NonexistentParam)

    I disagree with respect to Write-Output -InputObject -NonexistentParameter (as stated, the use of -InputObject unambiguously makes the following token its value, so -NonexistentParam should print, as happens now, but inconsistently), but #4578 will hopefully bring clarity.

  10. lzybkr commented on Aug 15, 2017

    @lzybkr
    Contributor

    Feel free to continue discussing, but I'm closing the issue because I can't see adding more complexity to the parameter binder to change this behavior.

    The "workaround" is in my opinion the preferred style - use quotes.

    There is no confusion to human readers.
    There is no potential ambiguity for the parameter binder to deal with.
    The script is future proof, adding a new parameter that might match some argument won't cause a problem.

  11. markekraus commented on Aug 15, 2017

    @markekraus
    Contributor

    Michael Klement (@mklement0)

    as stated, the use of -InputObject unambiguously makes the following token its value, so -NonexistentParam should print, as happens now, but inconsistently

    This may be unambiguous from a code standpoint. But from a user standpoint, that would be really confusing.

    Remove-Item -force -Path -wwhatif
    Remove-Item -force -Path -whatiff
    Remove-Item -force -Path -watif

    Now my very important -wwhatif file is deleted. :( Maybe I was just trying to test out the command but flubbed the -whatif parameter. My expectation as a user would be that I should be told -Path is missing its value or that -wwhatif is not a valid paramter, not that -wwhatif is either deleted or not found.

  12. mklement0 commented on Aug 16, 2017

    @mklement0
    ContributorAuthor

    Mark Kraus (@markekraus):

    That's a good example, and I can see how requiring quoting ultimately makes for a user-friendlier solution.

    Jason Shirk (@lzybkr) seems to agree with you, so this means that the unquoted---prefixed-bareword-as-value loophole should be closed: #4591

    For the sake of a sane OUTSIDE CLI, however, I hope that something like powershell -noprofile -file -t.ps1 will continue to work from outside of PowerShell.

  13. PetSerAl commented on Aug 16, 2017

    @PetSerAl
    Contributor

    Another workaround would be to use parameter with explicit argument syntax:

    Write-Host -Object: -NoNewline
  14. mklement0 commented on Aug 20, 2017

    @mklement0
    ContributorAuthor

    Mark Kraus (@markekraus):

    Actually, on further reflection, I do not think that something like

    Remove-Item -force -Path -wwhatif

    is a good reason to abstain from using the definition of -Path determine the syntactic role of -wwhatif. An actual filename matching a misspelled -WhatIf is too obscure a scenario to drive the decision on how to properly parse such commands.

    Based on the subsequent discussion in #4591, I'm now more than ever convinced that the only predictable way to handle --prefixed barewords is as I've originally proposed, given the desire to retain the ability to use such barewords, in the context of ValueFromRemainingArguments.

    Please see #4624

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

    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifWG-Languageparser, language semantics

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions