Repository navigation
Inconsistent, unpredictable behavior of "-"-prefixed barewords in argument mode #4624
Description
Context:
Note: By bareword I mean a run of non-whitespace characters not single- or double-quoted as a whole (e.g., foo), used in argument mode, and normally interpreted as an expandable string (e.g., $HOME/boy).
While --prefixed barewords look like parameter names (e.g.,-foo), they sometimes act as arguments (values to bind to parameters), and the ability to use them as such is important in two contexts:
-
In the context of the external CLI, when passing the name of a script file that happens to start with
-; e.g.,powershell -File -foo.ps1 -
When passing arguments through that themselves happen to be command line or command-line fragments, which is enabled by cmdlets/advanced functions that declare a
ValueFromRemainingArgumentsparameter; e.g.,
Get-Command -Syntax Get-ChildItem -Path cert:passes-Path cert:through (the-Pathbareword is not interpreted asGet-Command's parameter) in order to discover the certificate provider's dynamic parameters.
Note that, except when using the external CLI, you can always use quoting to explicitly mark a bareword as an argument.
Generally, though, if there's no ambiguity, not having to quote tokens that don't strictly need it is a matter of convenience.
The problem:
There are two major problems with how --prefixed barewords are currently parsed and interpreted:
-
Pragmatically speaking, their syntactic role (parameter name vs. argument) is virtually unpredictable by the end user.
- E.g., the
-ainGet-Item -LiteralPath -ais an argument, whereas the-cinGet-Item -LiteralPath -cis parameter name-Credential.
- E.g., the
-
They are not parsed and expanded in the same way as barewords that do not start with
-.- E.g., bareword
a$HOMEexpands$HOME, while-a$HOMEdoes not.
- E.g., bareword
More examples below.
Solution options:
-
Leave things as they are and document the current behavior, recommending that quoting be used for predictability.
-
Change the parameter binding as proposed in Parameter values disambiguated with their explicit parameter names should not require quoting #4576: Once a parameter has been (unambiguously) identified, its specific definition - switch vs. parameter-with-argument- alone determines whether the next token is its argument or a different parameter:
-
E.g., in
Get-Item -LiteralPath -p,-pwould unambiguously be an argument, because-LiteralPathsyntactically requires an argument.-
An argument can be made that in cases where the user intent was likely different - e.g.,
Get-Item -LiteralPath -Path- that unconditional interpretation of--prefixed barewords as parameter names is preferable in terms of the user experience (Missing an argument for parameter 'LiteralPath'as opposed toCannot find path '-Path' because it does not exist); a point that @markekraus has made. -
That said, I think the conceptual simplicity and clarity of the let-the-identified-parameter-determine-the-next-token's-syntactic-role approach is more beneficial in the grand scheme of things.
-
-
Note: @lzybkr has already discounted this option based on implementation cost, but I'm resubmitting it for consideration now that we have a fuller picture of all the issues involved:
-
I can't see adding more complexity to the parameter binder to change this behavior.
-
Disallow
--prefixed barewords altogether, as proposed in "-"-prefixed barewords in argument mode should always be interpreted as parameter names #4591: In order for a--prefixed tokens to be recognized as an argument, it would have to be single- or double-quoted .-
E.g.,
Get-Item -LiteralPath -awould then break, becauseGet-Itemhas no-a*parameter, andGet-Item -LiteralPath '-a'would have to used consistently. (But, in contrast, PowerShell's outside CLI would still need to accept something like-File -a.) -
The convenience of being able to specify
--prefixed tokens as arguments is lost, but at least the behavior is predictable.- That said, the waters would still be muddy with respect to commands that have a
ValueFromRemainingArgumentsparameter, where, based on the current behavior, a--prefixed barewords if it happens not to match a regular parameter would still be interpreted as an argument.
- That said, the waters would still be muddy with respect to commands that have a
-
Examples:
### Virtually unpredictable syntactic role
# BREAKS
# -c, because it happens to match parameter name -Credential, is interpreted as a *parameter name*.
# Note that, purely based on -LiteralPath being a parameter that *requires an argument*, there is
# no good reason to parse -c as a parameter name. The syntax of Get-Item unambigously implies that
# the token following -LiteralPath must be an *argument*.
Get-Item -LiteralPath -c
# SUCCEEDS
# -a, because it happens not to match any of Get-Item's parameters, is interpreted as an *argument*
Get-Item -LiteralPath -a
# Even though a "-"-prefixed token as the 1st argument is normally *invariably* interpreted as
# a *parameter name*, that is not the case for cmdlets with a ValueFromRemainingArguments parameter,
# such as Write-Output:
# BREAKS
# -n happens to match parameter (switch) -NoEnumeration, and the call is therefore missing
# arguments to output (-InputObject)
Write-Output -n
# Solution: Use -- to signal the start of all positional arguments.
Write-Output -- -n
# SUCCEEDS, because -z happens not to match a parameter name and is therefore bound to -InputObject.
Write-Output -z
### Parsing / expansion inconsistencies
# As part of an array, a "-"-prefixed bareword doesn't work as the 1st element.
Write-Output -a, b # BREAKS
Write-Output a, -b # SUCCEEDS
# "-"-prefixed barewords are non-expanding
Write-Output a$HOME # expands $HOME
Write-Output -a$HOME # doesn't expand $HOME
# A "-"-prefixed bareword containing a "."-prefixed suffix is parsed as *2* arguments
Get-Item -LiteralPath a.ps1 # SUCCEEDS
Get-Item -LiteralPath -a.ps1 # BREAKS: Get-Item receives *2* arguments: '-a' and '.ps1'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
markekraus commentedon Aug 20, 2017
Michael Klement (@mklement0)
As a point of clarity, this is not something under PowerShell's control as the calling shell (bash, another PowerShell, CMD, ksh, etc) has final say in the matter. What you are specifically requesting here is to maintain the current argument parsing for arguments passed from the calling shell to the PowerShell binary upon initialization. Correct?
mklement0 commentedon Aug 20, 2017
Mark Kraus (@markekraus):
Correct:
powershell -File -foo.ps1currently works already, and should continue to work.(And as one aspect of conceptual simplicity and consistency, the same should work predictably PowerShell-internally as well (when calling functions/cmdlets)).
Calling from a POSIX-like shell (Unix; e.g.,
bash),powershell -File -foo.ps1is indistinguishable from variantspowershell -File '-foo.ps1'andpowershell -File "-foo.ps1", because the POSIX-like shell performs quote removal before passing the array of arguments as literals to the target command:powershellsees just literals-Fileand-foo.ps1in all cases.Conceivably, PowerShell could - but shouldn't, in my view - then place additional requirements on the arguments passed, based on its syntax requirement, such as for the received
-foo.ps1argument to still be represented as'-foo.ps1', for instance, ifthe decision were made (I hope not) that
--prefixed tokens are only recognized as arguments when quoted.If such a requirement existed, you'd have to call
powershell -File "'-foo.ps1'"(orpowershell -File '"-foo.ps1"') - and such two-unrelated-quoting-layers awkwardness is best avoided.Unfortunately, we already have that awkwardness with respect to how
-Commandparsing works: because it assembles all arguments into a single string that it then interprets based on PowerShell rules, you do need two-layer quoting; e.g.:See #4024 (comment) for more.
microsoft-github-policy-service commentedon Nov 16, 2023
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.
microsoft-github-policy-service commentedon Nov 16, 2023
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.
microsoft-github-policy-service commentedon Nov 16, 2023
This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.
microsoft-github-policy-service commentedon Nov 23, 2023
This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.