Search code examples
powershellsubstring

Can you use a negative number in substring in Powershell?


Can you use a negative number in the string length attribute for Substring in PowerShell? For example:

$str.Substring($startPos, -3)

I read that in PowerShell 7 reading backwards may have been added ("negative values return the amount of substrings requested starting from the end of the input string") but it looked like that was for Split.

Currently, I cannot upgrade to v7. I am on v4. Is it possible to do this in Version 4?

OR is there another way to read backwards on a string in PowerShell v4?

Example:

I need to replace ABCD, but because of the commas and lack of consistent data points, it's very hard for me to find a consistent index before ABCD. Hence, why I use LastIndexOf to find the last comma. Now, I am hoping I can read backwards the length of $replaceStr from the last comma, if that makes sense. $replaceStr.length is what I was hoping to make negative.

$str = '12345,FOO FOO,ABCD,12351235'
$replaceStr = #code to get value of string that needs replaced
$str.Replace($str.Substring($str.LastIndexOf(',') + 1, $replaceStr.length), $Value)

Import-Csv method (mentioned in comments)

This is my PowerShell to import the CSV, replace name, and then Set-Content to the original file. When I Set-Content the CSV file's output is empty.

#gets all the full paths of each csv file
$files = (Get-ChildItem $path -Filter '*.csv').FullName

#loops through all found csv files
$files | Foreach-Object {
  #imports current csv file
  $csv = Import-Csv $_ -Header foo, foofoo, name, foobar
  
  #loops through csv 
  $csv | Foreach-Object {
    #replaces .name with $replaceStr
    $str.Replace($_.name, $replaceStr)
    #set content of csv file
  } | Set-Content -Path $_
}

Here is an example of my CSV file:

12345,FOO FOO,ABCD,12351235
54321,FOO BAR,ABCD,56785678
23232,FOO,ABCD,98765432
00000,FOO FOO BAR,ABCD,123456789

Solution

  • Trying to not overcomplicate the code, you might find it easier if you transform your string into an array via splitting on , then updating the 2nd to last token and lastly joining it back with , as delimiter:

    $str = '12345,FOO FOO,ABCD,12351235'
    $tokens = $str.Split(',')
    $tokens[-2] = 'myNewValue'
    $str = $tokens -join ','
    $str # 12345,FOO FOO,myNewValue,12351235
    

    You're really close with your example treating the files as CSV, a minor update would fix the problem:

    Get-ChildItem $path -Filter '*.csv' | Foreach-Object {
        $csv = $_ | Import-Csv -Header col1, col2, col3, col4
        $csv | ForEach-Object {
            # update the value of this property
            $_.col3 = $replaceStr
            # then output the updated object
            $_
        } | ConvertTo-Csv |
            # here we're skipping the headers
            Select-Object -Skip 1 |
            Set-Content -LiteralPath $_.FullName
    }
    

    To update the value of a property since now you're dealing with objects due to Import-Csv you can simply refer to the property name to be updated and assign it a new value: $obj.Property = 'mynewvalue'. You would also need to convert these objects back to a CSV representation, for that you would be using ConvertTo-Csv and lastly skip the headers with Select-Object and save it back with Set-Content.