How to replace character in Powershell with initial character + a space - powershell

Hopefully this question isn't already answered on the site. I want to replace every number in a string with that number and a space. So here's what I have right now:
"31222829" -replace ("[0-9]","$0 ")
The [0-9] looks for any numbers, and replaces it with that character and the space. However, it doesn't work. I saw from another website to use $0 but I'm not sure what it means.The output I was looking for was
3 1 2 2 2 8 2 9
But it just gives me a blank line. Any suggestions?
LardPies

This probably isn't the right way to do it, but it works.
("31222829").GetEnumerator() -join " "
The .GetEnumerator method iterates over each character in the string
The -join operator will then join all of those characters with the " " space

tl;dr
PS> "31222829" -replace '[0-9]', '$& '
3 1 2 2 2 8 2 9
Note that the output string has a trailing space, given that each digit in the input ([0-9]) is replaced by itself ($&) followed by a space.
As for what you tried:
"31222829" -replace ("[0-9]","$0 ")
While enclosing the two RHS operands in (...) doesn't impede functionality, it's not really helpful to conceive of them as an array - just enumerate them with ,, don't enclose them in (...).
Generally, use '...' rather than "..." for the RHS operands (the regex to match and the replacement operand), so as to prevent confusion between PowerShell's string expansion (interpolation) and what the -replace operator ultimately sees.
Case in point: Due to use of "..." in the replacement operand, PowerShell's string interpolation would actually expand $0 as a variable up front, which in the absence of a variable expands to the empty string - that is why you ultimately saw a blank string.
Even if you had used '...', however, $0 has no special meaning in the replacement operand; instead, you must use $& to represent the matched string, as explained in this answer.
To unconditionally separate ALL characters with spaces:
Drew's helpful answer definitely works.
Here's a more PowerShell-idiomatic alternative:
PS> [char[]] '31222829' -join ' '
3 1 2 2 2 8 2 9
Casting a string to [char[]] returns its characters as an array, which -join then joins with a space as the separator.
Note: Since -join only places the specified separator (' ') between elements, the resulting string does not have a trailing space.

You can use a regex with positive lookahead to avoid the trailing space. Lookahead and lookbehind are zero-length assertions similar to ^ and $ that match the start/end of a line. The regex \d(?=.) will match a digit when followed by another character.
PS> '123' -replace '\d(?=.)', '$0 '
1 2 3
To verify there's no trailing space:
PS> "[$('123' -replace '\d(?=.)', '$0 ')]"
[1 2 3]

Related

Renaming the 4th character through the 12th

Here is what I have.
get-childitem "\\myfileserver\out\*" | foreach { rename-item $_ $_.Name.Replace("_123456_837P.", ".").Replace(".test.", ".sa.").Replace("_987654_837I." , ".") }
Here is the filename I want to fix
999_987654_837I.74161.test
I want to remove _987654_837I from the file name. I was just going to rename it but those numbers may change. So now I want to remove the 4th character starting at the _ and back to the "I" or 9th character.
You can use a regex pattern to get the required part.
See regex example + explanation:
https://regex101.com/r/xNoBVD/2
I use positive lookbehind to force regex to get the first 3 characters at the very beginning of the line (^) without capturing it. The following 12 characters are captured and can then be replaced with ''
$regexReplacePattern = '(?<=^.{3}).{12}'
'999_987654_837I.74161.test' -replace $regexReplacePattern, ''

Powershell - Split a string on a character

I've got a CSV file full of filepaths like below:
D:\CompanyData\REPORTS\ENQUIRIES\Old House Farm\Images
D:\CompanyData\REPORTS\ENQUIRIES\Quay House\Text
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\Photography
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\Reports\
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\Reports\Images
I want to split them on the 5th '/' character to return the following (including the last trailing '/'
D:\CompanyData\REPORTS\ENQUIRIES\Old House Farm\
D:\CompanyData\REPORTS\ENQUIRIES\Quay House\
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\
So far I've tried the following:
$source = $Item.Source.Split("\")[0]
And various other combinations of the above but can't quite get what I'm after. Can anyone assist?
Try like this:
[string](Split-Path "PATH")+"\"
If you have $Item.Source then:
[string](Split-Path "$($Item.Source)")+"\"
Here is another solution using Select-String
(Note, that the pattern uses regex, so you need to escape the backslash \ with another backslash \)
$source = ($($Item.Source) | Select-String -Pattern '.+\\.+\\.+\\.+\\.+\\' -AllMatches).Matches.Value
Output:
D:\CompanyData\REPORTS\ENQUIRIES\Old House Farm\
D:\CompanyData\REPORTS\ENQUIRIES\Quay House\
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\Reports\
D:\CompanyData\REPORTS\ENQUIRIES\Church Road\Reports\
You can split each file path on the backslash to get an array of parts. Then join a maximum of 5 parts with a backslash and append another backslash tyo that:
$parts = "D:\CompanyData\REPORTS\ENQUIRIES\Old House Farm\Images" -split '\\'
'{0}\' -f ($parts[0..[math]::Min($parts.Count, 4)] -join '\')
Or do this with regex:
"D:\CompanyData\REPORTS\ENQUIRIES\Old House Farm\Images" -replace '^(([^\\]+\\){1,5}).*', '$1'
Regex details:
^ Assert position at the beginning of the string
( Match the regular expression below and capture its match into backreference number 1
( Match the regular expression below and capture its match into backreference number 2
[^\\] Match any character that is NOT a “A \ character”
+ Between one and unlimited times, as many times as possible, giving back as needed (greedy)
\\ Match the character “\” literally
){1,5} Between one and 5 times, as many times as possible, giving back as needed (greedy)
)
. Match any single character that is not a line break character
* Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
Result:
D:\CompanyData\REPORTS\ENQUIRIES\Old House Farm\
Simple regex version:
$Item.Source -replace "(('[^\\]+\\){1,5}).*", '$1'
[^\\]+\\ matches 1-n non-\ followed by \
{1,5} repeat the pattern 5 times

A Regex pattern to split (possibly negative) integers separated with a hyphen

I need to split (possibly negative) integers separated with a hyphen and then turn those numbers into a range. I can do the range part, the "split" boggles me.
"8-12" # output = 8, 12
"-4--2" # output = -4, -2
Depending on your inputs, following could suffice
"8-12" -replace '(\d)-', '$1, '
"-4--2" -replace '(\d)-', '$1, '
The gist of this is to search for a decimal, capturing it in a group, followed by a hyphen. Replace each match with the captured group (the decimal), a comma and a space.
Use a lookbehind assertion to only split on - when preceded by a digit:
PS C:\> '-4--12' -split '(?<=\d)-'
-4
-12

Strange result from String.Split()

Why does the following result in an array with 7 elements with 5 blank? I'd expect only 2 elements.
Where are the 5 blank elements coming from?
$a = 'OU=RAH,OU=RAC'
$b = $a.Split('OU=')
$b.Count
$b
<#
Outputs:
7
RAH,
RAC
#>
In order to split by strings (rather than a set of characters) and/or regular expressions, use PowerShell's -split operator:
PS> ('OU=RAH,OU=RAC' -split ',?OU=') -ne '' # parentheses not strictly needed
RAH
RAC
-split by default interprets its RHS as a regular expression, and ,?OU= matches both OU by itself and ,OU, resulting in the desired splitting, returning the tokens as an array.
For all features supported by -split, including literal string matching, limiting the number of tokens returned, and use of script blocks, see Get-Help about_split.
Since the input starts with a match, however, -split considers the first element of the split to be the empty string. By passing the resulting array of tokens to -ne '', we filter out these empty strings.
By contrast, in Windows PowerShell use of the .NET (FullCLR, up to 4.x) String.Split() method, as you've tried, works very differently:
'OU=RAH,OU=RAC'.Split('OU=')
OU= is interpreted as an array of characters, any of which, individually acts as separator - irrespective of the order in which the characters are specified. Leading, adjacent, and trailing separators are by default considered to separate empty tokens, so you get an array of 7 tokens:
#( '', '', '', 'RAH,', '', '', 'RAC')
Note to PowerShell Core users (PowerShell versions 6 and above):
The .NET Core String.Split() method now does have a scalar [string] overload that looks for an entire string as the separator, which PowerShell Core selects by default; to get the character-array behavior described, you must cast to [char[]] explicitly:
'OU=RAH,OU=RAC'.Split([char[]] 'OU=')
If you construct the .Split() method call carefully, you can specify strings, but note that you still don't get regular-expression support:
PS> 'OU=RAH,OU=RAC'.Split([string[]] 'OU=', 'RemoveEmptyEntries')
RAH,
RAC
works to split by literal string OU=, removing empty entries, but as you can see, that doesn't allow you to account for the ,
You can take this further by specifying an array of strings to split by, which works in this simple case, but ultimately doesn't give you the same flexibility as the regular expressions that PowerShell's -split operator provides:
PS> 'OU=RAH,OU=RAC'.Split([string[]] ('OU=', ',OU='), 'RemoveEmptyEntries')
RAH
RAC
Note that specifying an (array of) strings requires the 2-argument form of the method call, meaning you must also specify a System.StringSplitOptions enumeration value. Use 'None' to not apply any options (as of this writing, the only true option that is supported is 'RemoveEmptyEntries', as used above).
(The type-safe way to specify option is to use, e.g., [System.StringSplitOptions]::None, however, passing the option name as a string is a convenient shortcut; e.g., 'None'.)
It splits the string for each character in the separator. So its splitting it on 'O', 'U' & '='.
As #mklement0 has commented, my earlier answer would not work in all cases. So here is an alternate way to get the expected items.
$a.Split(',') |% { $_.Split('=') |? { $_ -ne 'OU' } }
This code will split the string, first on , then each item will be split on = and ignore the items that are OU, eventually returning the expected values:
RAH
RAC
This will work even in case of:
$a = 'OU=FOO,OU=RAH,OU=RAC'
generating 3 items FOO, RAH & RAC
To get only 2 string as expected you could use following line:
$a.Split('OU=', [System.StringSplitOptions]::RemoveEmptyEntries)
Which will give output as:
RAH,
RAC
And if you use (note the comma in the separator)
$a.Split(',OU=', [System.StringSplitOptions]::RemoveEmptyEntries)
you will get
RAH
RAC
This is probably what you want. :)
Never mind. Just realised it looks for strings on either side of 'O', 'U', and '='.
There are therefore 5 blank chars (in front of the first 'O', between 'O' and 'U', between 'U' and '=', between the second 'O' and 'U', between the second 'U' and '=').
String.Split() is character oriented. It splits on O, U, = as three separate places.
Think of it as intending to be used for 1,2,3,4,5. If you had ,2,3,4, it would imply there were empty spaces at the start and end. If you had 1,2,,,5 it would imply two empty spaces in the middle.
You can see with something like:
PS C:\> $a = 'OU=RAH,OU=RAC'
PS C:\> $a.Split('RAH')
OU=
,OU=
C
The spaces are R_A_H and R_A. Split on the end of a string, it introduces blanks at the start/end.
PowerShell's -split operator is string oriented.
PS D:\t> $a = 'OU=RAH,OU=RAC'
PS D:\t> $a -split 'OU='
RAH,
RAC
You might do better to split on the comma, then replace out OU=, or vice versa, e.g.
PS D:\t> $a = 'OU=RAH,OU=RAC'
PS D:\t> $a.Replace('OU=','').Split(',')
RAH
RAC

$macro substitution - ExpandString limitations

I am trying to implement macro replacement based on this discussion. Basically it works, but seems the ExpandString have some limitatoins:
main.ps1:
$foo = 'foo'
$text = [IO.File]::ReadAllText('in.config')
$ExecutionContext.InvokeCommand.ExpandString($text) | out-file 'out.config'
in.config (OK):
$foo
in.config (Error: "Encountered end of line while processing a string token."):
"
in.config (Error: "Missing ' at end of string."):
'
The documentation states:
Return Value: The expanded string
with all the variable and expression
substitutions done.
What is 'expression substitution' (may be this is my case)?
Is there some workaround?
The error is occurring because quotes (single and double) are special characters to the PowerShell runtime. They indicate a string and if they are to be used as just that character, they need to be escaped.
A possible workaround would be to escape quotes with a backtick, depending on your desired result.
For example if my text file had
'$foo'
The resulting expansion of that string would be
PS>$text = [io.file]::ReadAllText('test.config')
PS>$ExecutionContext.InvokeCommand.ExpandString($text)
$foo
If you wanted to have that variable expanded, you would need to escape those quotes.
`'$foo`'
PS>$text = [io.file]::ReadAllText('test.config')
PS>$ExecutionContext.InvokeCommand.ExpandString($text)
'foo'
or if you were going to have an unpaired single or double quote, you would need to escape it.
You could do a -replace on the string to escape those characters, but you'll have to make sure that is the desired effect across the board.
PS>$single, $double = "'", '"'
PS>$text = [io.file]::ReadAllText('test.config') -replace "($single|$double)", '`$1'
PS>$ExecutionContext.InvokeCommand.ExpandString($text)
NOTE: After you do the ExpandString call, you won't have the backticks hanging around anymore.