PowerShellPracticeAndStyle
PowerShellPracticeAndStyle copied to clipboard
Unquoted strings
PowerShell allows for unquoted strings as shown in the Style-Guide/Naming-Conventions:
# Instead write:
Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath README.md)
But there are limitations to using unquoted strings as e.g. spaces are not allowed in an unquoted value and will obviously fail if not quoted (with either single or double quotes, see the related issue: #63):
# Wrong:
Get-Content -Path (Join-Path -Path $PSScriptRoot -ChildPath READ ME.md)
Besides using unquoted strings in argument values, unquoted strings are common (best practice?) in hash tables keys. Hash tables are major objects in PowerShell along with PSCustomObjects which are in fact constructed from a hash table. Hash tables are also used for things like splatting and PowerShell Data files.
As with argument values, the use of unquoted hash table keys is limited to certain characters, as e.g.:
# Wrong:
$Options = @{
Margin = 2
Padding = 2
Font-Size = 24
}
Will cause an error:
Line |
4 | Font-Size = 24
| ~
| Missing '=' operator after key in hash literal.
In some cases there are no errors but pitfalls, like:
$Hashtable = @{
U2 = 'String'
2U = 'UInt32'
}
$Hashtable.keys
2
U2
AFAIK, there is no clear definition of when I should use quoted keys (or argument values) or when I might not. In other words: which characters are allowed for unquoted strings (or: which characters are not allowed for unquoted strings)? It is also not described in About Quoting Rules or in About Special Characters (Assuming I am right, should I created a separate documentation issue at the PowerShell Team?)
So usually I use quotes on a trial-on-error bases or pro-actively on a better-safe-than-sorry bases. But that could cause some inconsistency in defining the best (readable) practice to resolve the above error:
$Options = @{
Margin = 2
Padding = 2
'Font-Size' = 24
}
Or:
$Options = @{
'Margin' = 2
'Padding' = 2
'Font-Size' = 24
}
I am especially concerned with this writing style / best practice issue because I am maintaining a ConvertTo-Expression script. Although the code might not follow the styling guidelines (yet), I am committed to follow them for the expression it outputs as much as possible. For this project, I got the issue request: Remove quotes in HashTables Keys when possible and looking for some documented guidance.
https://github.com/PowerShell/PowerShell/issues/6467 tries to give a comprehensive overview of the current behavior with respect to arguments in command parsing mode - however, it doesn't cover the hashtable-key issue (see below).
about_Quoting_Rules is probably the best place to document this, though it is currently being discussed in the larger context of parsing modes in https://github.com/MicrosoftDocs/PowerShell-Docs/issues/3440
As for use of unquoted key names in hash tables:
Without having looked at the source code, my understanding is as follows:
-
If a key is a syntactically valid number literal, it is used as a number - including with suffixes such as
u(U) for[uint32]and binary multiplier suffixes such askb, as well as hex. numbers (e.g.,0x10) or numbers in exponential notation (e.g.,1e2- , it is used as a number (this explains the2Ukey turning into2of typeSystem.UInt32in your example). -
If a key starts with a digit, but is otherwise not a syntactically valid number literal, the hashtable definition fails.
- Note: digit refers not just to the Latin decimal digits, but to all digits in the
DecimalDigitNumber(Nd) Unicode category, which includes characters such as๐(THAI DIGIT ZERO,U+0E50)
- Note: digit refers not just to the Latin decimal digits, but to all digits in the
-
Otherwise, it is interpreted as a string, but parsing only succeeds if the token is limited to a sequence of the following characters (these seem to be the same that can be used in variable names without needing to enclose the name in
{...}- see theabout_Variableshelp topic's Variable Names that Include Special Characters section):-
Characters from the following Unicode categories (defined in .NET as enumeration
System.Globalization.UnicodeCategorythe two-letter shorthands listed in parentheses can be used with\p{<shortCategoryName>}in regular expressions):UppercaseLetter(Lu)LowercaseLetter(Ll)TitlecaseLetter(Lt)ModifierLetter(Lm)OtherLetter(Lo)DecimalDigitNumber(Nd) - except as the first character (variable names don't have this restriction)
-
_(underscore, the only allowed symbol)
-
To put it in terms of guidance:
Quote your hash-table keys, if:
-
they should be strings but happen to start with a digit.
-
they are strings that contain whitespace or a symbol other than
_
@mklement0, thank you for the comprehensive information, it is very helpful.
It's worth pointing out that this same advice applies whenever you're interacting with a hashtable -- even when you're calling $hashtable.Add( or getting or setting via dot notation or indexing. I.e.:
$hashtable[2u] = "UInt"
$hashtable.2u
Also worth noting is that good syntax highlighting (such as at the prompt in PSReadline) will make the distinction really obvious:
