$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 "" 
3

5 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.

3

Lee_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 exactly 4 characters (.) (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) } 

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.