1

powershell string split

In this picture, powerhsell has split each letter of target, but I really want the string target as a whole to be split. How do I do that?

powershell
PS C:\Users\Administrator> "source target destination".tolower().Split("target")
sou
c
         
    
    
    
    
d
s
in
    
ion
      

I tried using regular expressions without success. Do you have a solution?

Luuk
  • 12,245
  • 5
  • 22
  • 33
apython
  • 23
  • 4
  • Please add your code as text. Please do not try to add it as image (your picture seems to be missing?) (more info: [Why should I not upload images of code/data/errors?](https://meta.stackoverflow.com/questions/285551/why-not-upload-images-of-code-errors-when-asking-a-question)) – Luuk May 13 '23 at 08:39
  • It's been adjusted. Take a look – apython May 13 '23 at 09:11
  • I cannot reproduce, see: https://tio.run/##K8gvTy0qzkjNyfn/X6k4v7QoOVWhJLEoPbVEISW1uCQzL7EkMz9PSa8kPwekUkNTL7ggJ7NEQwmiSEmT6/9/AA – Luuk May 13 '23 at 09:20
  • wait, it is reproduceable with Powershell5, not with [Powershell 7.3](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.3). Is upgrading a possibility ? – Luuk May 13 '23 at 09:22
  • 1
    or use: `"source target destination".tolower() -Split "target"` – Luuk May 13 '23 at 09:25
  • 1
    Can you add the expected output? (Just to make the question more clear) – Luuk May 13 '23 at 09:36
  • I'm using win10 powershell. Version 5.1.I found no problem with powershell core, but using the split function in the powershell desktop version gives different results. So you're probably using powershell core – apython May 15 '23 at 01:35

3 Answers3

3

The implication is that you're using Windows PowerShell, which is built on .NET Framework, where the [string] type's .Split() method has no overload for a single string argument constituting the desired separator.

Such functionality is only available via a string array, which then also requires specifying options:

# Required in Windows PowerShell
# Note the [string[]] cast, and the required options argument, 'None'
"source target destination".tolower().Split([string[]] "target", 'None')

If you specify just "target", as in your attempt, Windows PowerShell chooses the char[] overload, i.e. it splits your string into an array of characters of which each is treated as a separator - which explains the symptom you saw.

That is, in Windows PowerShell your attempt is equivalent to:

"source target destination".tolower().Split([char[]] "target")

Note that "source target destination".tolower().Split("target") does work the way you want in PowerShell (Core) 7+, where the underlying .NET (Core) runtime now does have an overload that accepts a single [string] (with an optional second parameter for the splitting options).


Taking a step back:

The following works in both PowerShell editions:

# Note: 'target' is interpreted as a *regex* by default
"source target destination".tolower() -split 'target'

That is, use of the PowerShell-native -split operator avoids the pitfall of changes in the underlying .NET framework resulting in changes that are outside of PowerShell's control (the only way to avoid them is to always cast method arguments to their exact type).

Note that -split:

  • by default treats its RHS operand as a regex, which makes it more flexible than the .Split() method; a literal-string opt-in is available (e.g.,
    'a.b' -split '.', 0, 'SimpleMatch')

  • is case-insensitive, as PowerShell generally is (unlike .NET); use the -csplit variant for case-sensitive matching.

The pitfall at hand, along with -split's enhanced capabilities, make it advisable to prefer -split over .Split() in general and, more generally - where feasible - choose PowerShell operators over .NET method calls for long-term stability. See this answer for a detailed discussion.

mklement0
  • 382,024
  • 64
  • 607
  • 775
3

Windows PowerShell

Your code sample:

PS> "source target destination".ToLower().Split("target")

is equivalent to:

PS> "source target destination".ToLower().Split( @("t", "a", "r", "g", "e", "t") )

because PowerShell is invoking the string[] Split(Params char[] separator) overload of Split which is described as:

Splits a string into substrings based on specified delimiting characters.

Basically, there's no overload of Split that takes a just a string, so Windows PowerShell is trying to be helpful and finds the next best thing, which it decides is string[] Split(Params char[] separator) because string implements System.Collections.Generic.IEnumerable<char> which means "target" can be converted to @("t", "a", "r", "g", "e", "t") to call that overload:

public sealed class String : ICloneable, IComparable, IComparable, IConvertible, IEquatable, System.Collections.Generic.IEnumerable

The result is that the string gets split at all instances of the individual characters in "target" rather than the entire word as a whole.

If you want to split on the contiguous string "target" you can... target ... the string[] Split(string[] separator, System.StringSplitOptions options) overload instead as follows:

PS> "source target destination".ToLower().Split("target", "None")
source
 destination

And if you want to see all the available overloads of Split you can do this:

PS> "".Split

OverloadDefinitions
-------------------
string[] Split(Params char[] separator)
string[] Split(char[] separator, int count)
string[] Split(char[] separator, System.StringSplitOptions options)
string[] Split(char[] separator, int count, System.StringSplitOptions options)
string[] Split(string[] separator, System.StringSplitOptions options)
string[] Split(string[] separator, int count, System.StringSplitOptions options)

which just confirms there's no overload of the form string[] Split(string separator).

As an aside, if you want to trim the substrings you can do:

PS> "source target destination".ToLower().Split(@("target"), "None").Trim()
source
destination

which uses Member Access Enumeration to invoke Trim() on all of the substrings.

PowerShell Core

PowerShell Core makes slightly different decisions about what overload of Split to invoke because it has different overloads available in the underlying dotnet runtime compared to Windows PowerShell:

PS> "".Split

OverloadDefinitions
-------------------
string[] Split(char separator, System.StringSplitOptions options = System.StringSplitOptions.None)
string[] Split(char separator, int count, System.StringSplitOptions options = System.StringSplitOptions.None)
string[] Split(Params char[] separator)
string[] Split(char[] separator, int count)
string[] Split(char[] separator, System.StringSplitOptions options)
string[] Split(char[] separator, int count, System.StringSplitOptions options)
string[] Split(string separator, System.StringSplitOptions options = System.StringSplitOptions.None)
string[] Split(string separator, int count, System.StringSplitOptions options = System.StringSplitOptions.None)
string[] Split(string[] separator, System.StringSplitOptions options)
string[] Split(string[] separator, int count, System.StringSplitOptions options)

The overload string[] Split(string separator, System.StringSplitOptions options = System.StringSplitOptions.None) now has a default value of None for the options parameter which means PowerShell Core can bind to this overload instead and the result is:

PS> "source target destination".ToLower().Split("target")
source
 destination

and as an aside again you can trim the values with this if you want:

PS> "source target destination".ToLower().Split(@("target"), "None").Trim()
source
destination

HTH.

mclayton
  • 8,025
  • 2
  • 21
  • 26
0

I think below command will yield the result you are looking for:

"source target destination" | ForEach-Object { $_.Split(' ')[1] }

In case you want to split the word target character by character then below approach can be followed:

$res ="source target destination" | ForEach-Object { $_.Split(' ')[1] }
$res.ToCharArray() 
Luuk
  • 12,245
  • 5
  • 22
  • 33
Verma
  • 1
  • 1
  • You think? why did you not try, and find out that it is incorrect: https://tio.run/##K8gvTy0qzkjNyfn/X6k4v7QoOVWhJLEoPbVEISW1uCQzL7EkMz9PSaFGwS2/yDUxOUPXPykrNblEoVpBJV4vuCAns0RDXUFdM9owVqGW6/9/AA – Luuk May 13 '23 at 09:30
  • @Luuk Just tried it and it worked. Sorry for not trying it at the first place. – Verma May 13 '23 at 09:31
  • I did (or do ?) read the question a splitting on "target", so I do think we have a language problem (between OP and /me .... ) – Luuk May 13 '23 at 09:34
  • @Luuk Not sure about that, but my answer was based on the understanding that the author of the question wants the word "target" to be displayed as output when he pass the string "source target destination". But if the author wants to split the word target character by character then below approach can be followed :$res ="source target destination" | ForEach-Object { $_.Split(' ')[1] } $res.ToCharArray() – Verma May 13 '23 at 09:50