$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 ""
When you want to extract a part of a string in PowerShell we can use the Substring() method. This method allows us to specify the start and length of the substring that we want to extract from a string.
To find a string inside of a string with PowerShell, you can use the Substring() method. This method is found on every string object in PowerShell. The first argument to pass to the Substring() method is the position of the leftmost character. In this case, the leftmost character is T .
If we want to only get the first line of a file we can use the Get-Content cmdlet and pipe this to the Select-Object cmdlet and use the First parameter to retrieve one line.
For example, if the password contains a dollar sign ($) it must either be preceded by a grave accent ` (also known as a backtick: ASCII code 96 - Alt+96) or the password encapsulated in single ' ' to pass the password to PowerShell exactly as entered.
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.
Just do it:
$a.Substring(0, [Math]::Min($a.Length, 20))
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With