Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Selecting first x characters in PowerShell with a catch

$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 ""
like image 476
Zach Avatar asked Oct 27 '18 10:10

Zach


People also ask

How do I get characters from a string in PowerShell?

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.

How do I get part of a string in PowerShell?

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 .

How do you select the first line in PowerShell?

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.

How do you pass special characters in PowerShell?

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.


3 Answers

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.

like image 75
Lee_Dailey Avatar answered Oct 10 '22 04:10

Lee_Dailey


Just do it:

$a.Substring(0, [Math]::Min($a.Length, 20))
like image 26
Esperento57 Avatar answered Oct 10 '22 04:10

Esperento57


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.

like image 42
mklement0 Avatar answered Oct 10 '22 06:10

mklement0