$a is going to be something like "Corners" or "special corners", but could be as long as something like "Standard Window Openings up to 37" which means a simple Substring() won't work (that I'm aware of anyway) if I want to find up to the first 20 characters in $a (that's the catch).
I found this bit is designed to do what I want, but it just gives me
"char[] ToCharArray(), char[] ToCharArray(int startIndex, int length)"
and I don't know how to make it function correctly.
($a.ToCharArray | select -First 20) -join "" 35 Answers
Just do it:
$a.Substring(0, [Math]::Min($a.Length, 20)) there is no .ToCharArray property for strings. what you used will give you the overloads for that method. [grin] try typing in a quoted string, adding a dot, and seeing what all shows up.
what you want is the .ToCharAray() method.
so, add those missing parens to the call and it will work. [grin]
also, you really ought to use the "in front" version of the -join string operator in that situation. the "behind" version is for adding a delimiter. look at the differences [both give the same result] ...
-join ('Standard Window Openings up to 37'.ToCharArray() | Select-Object -First 20)('Standard Window Openings up to 37'.ToCharArray() | Select-Object -First 20) -join ''
the 1st example is a better fit for your actual goal.
3Lee_Daily's helpful answer explains the problem with your attempt well and offers a working solution, including one using the unary form of -join.
Just to spell out for future readers why using just .Substring() is not an option for implementing extract-at most-N-chars. logic: attempting to extract a substring beyond the length of the input string causes an exception:
PS> 'abc'.Substring(0, 2) # OK ab PS> 'abc'.Substring(0, 4) # !! Exception, because at most 3 chars. can be extracted Exception [...]: Index and length must refer to a location within the string. [...] Your approach to solving that problem using a pipeline with Select-Object is a bit heavy-handed, though.
Below are better-performing alternatives that use expressions.
Esperento's helpful answer offers the best-performing solution, using .NET features only, though it is a bit "noisy" in that it requires a nested method call and a variable (rather than a literal) as input, because that variable must be referenced in the nested method call.
The approaches below are more PowerShell-idiomatic.
LotPings, in a comment, offers a concise solution based on the fact that strings can implicitly be treated as character arrays, so array slicing can be applied; note that indices are 0-based:
PS> -join 'abc'[0..1] ab PS> -join 'abc'[0..3] # equivalent of: 'abc'.Substring(0, 4), but by default without error abc Range expression 0..3 evaluates to array 0, 1, 2, 3, causing the characters at the specified indices to be returned as a character array which -join then reassembles into a string.
By default, PowerShell ignores indices outside the bounds of arrays, but the caveat is that if Set-StrictMode -Version 3 or higher is in effect, the above too will cause an error.
A better-performing alternative that is not sensitive to Set-StrictMode is to use the -replace operator with a regex (regular expression).
That said, this solution is somewhat obscure.
PS> 'abc' -replace '(?<=.{2}).+' # equivalent of 'abc'.Substring(0, 2) ab PS> 'abc' -replace '(?<=.{4}).+' # always-safe equivalent of 'abc'.Substring(0, 4) abc .{4}matches exactly4characters (.) (implicitly) at the start of the string, without including those characters in the match ((?<=...), a look-behind assertion);.+then matches all remaining characters (one or more).The net effect is that all input strings with more than 4 characters have everything from the 5th character replaced with the empty string (due to absence of a replacement operand), leaving effectively just the first 4 characters.
Input strings with 4 or fewer characters are passed through as-is (no extraction needed).
For multi-line input strings, a little more work is needed (inline option (?s) to make . match newlines (`n) too):
PS> "a`nbc" -replace '(?s)(?<=.{3}).+' # extract first 3 chars as string: "a`n" a b Also consider use of a simple helper function:
# Safe equivalent of: # $String.Substring($Start) # $String.Substring($Start, $Length) function substr ([string] $String, [int] $Start = 0, [int] $Length = -1) { if ($Length -eq -1 -or $Start + $Length -ge $String.Length) { $String.Substring($Start) } else { $String.Substring($Start, $Length) } } Note that you need to call it with argument syntax (shell-like, whitespace-separated arguments), as with any function in PowerShell:
PS> substr 'abc' -Length 2 # same as: substr abc 0 2 / substr -String abc -Start 0 -Length 2 ab PS> substr 'abc' -Length 4 abc Finally, note that there's an RFC for introducing string-manipulation cmdlets (yet to be implemented as of PowerShell Core 6.2.0-preview.1), which proposes a Get-Substring cmdlet, among others, for efficient substring manipulation in the pipeline.
Old Question, but including my .Net Regex Method in case it helps anyone.
[Regex]::Match($a,'.{1,4}').Value
If you don't want to worry about whether StrictMode -Version 3 or higher is in effect which will cause -join $a[0..19] to error on strings shorter than 20, you can just use an if statement
if ($a.Length -gt 20) { $a = $a.Substring(0,20) }