How to Remove Quotes and Replace the Delimiter in a File using PowerShell Preserving the Commas

Recently I was tasked with a file cleansing exercise. The endeavor looked simple, but the solution was not, I needed to prepare a file for processing, but wanted to first proof concept my approach so I opted to utilizing this opportunity to learn PoweShell.

More about the task: I was supplied with a series of CSV files in which the data looked like

123,ABC,XYZ

"1,2,3", ABC, "XY,Z"

123,"A,BC", XYZ

As you can see this is something I think we may call alternating double-quotes comma delimited file with embedded commas, I simply needed to remove the occasional quotes and replace the delimiter to pipe to be able to process the file preserving the original quotes. You may notice, I blogged in the previous post I was able to achieve the same using MS SSIS Script Transformation which basically uses some .Net code. To be fair, I did not come up with the algorithm, turned out my colleague Danny T. had a very extensive experience in textual data processing, so I was just the implementer.

The script turned out to work as expected, but alas there is a limitation to PowerShell’s get-content Cmdlet on how long data file it can digest. So the script may not be bale to process the entire input file. I guess the workaround is in using a revised version that utilizes the .Net stream class, hope somebody would share it with me someday.

So here is the script:

1: # Author: Arthur Zubarev

2: # Notes: Removes " and replaces the delimiter to |

3: # In DOS batch usage:

4: # Powershell.exe "& 'D:\PSScripts\RemoveQuotes-ReplaceDelim.ps1'" -InputCSV ""D:\Input.csv"" -NonInteractive -WindowStyle hidden -ExecutionPolicy RemoteSigned

5:  

6: param([string]$InputCSV = "")

7:  

8: # Temp assignment!

9: $InputCSV = "D:\Input.csv"

10:  

11: $process_yes_no = 0

12:  

13: if ( $InputCSV -eq "" ) {

14: Write-Host "Input parameter is missing. Usage: .\RemoveQuotes-ReplaceDelim.ps1 -InputCSV " -ForegroundColor red -BackgroundColor yellow

15: Exit

16: }

17:  

18: function GetScriptDirectory {

19: $Invocation = (Get-Variable MyInvocation -Scope 1).Value

20: Split-Path $Invocation.MyCommand.Path

21: }

22:  

23: Write-Host "Processing" $InputCSV -foregroundcolor green -backgroundcolor black

24:  

25: # make the "fixed" file up

26: $fixed_file = Join-Path(GetScriptDirectory) "fixed.csv";

27:  

28: Write-Host "Saving fixed file to" $fixed_file -foregroundcolor yellow -backgroundcolor black

29:  

30: # read the input csv as binary

31: $reader = [System.IO.File]::OpenText($InputCSV)

32: try {

33: for(;;) {

34: $to_repair_t = $reader.ReadLine()

35: if ($to_repair_t -eq $null) {

36: break

37: }

38: $to_repair = get-content -encoding byte $to_repair_t

39: $fixed = New-Object System.Collections.ArrayList

40: $byteEncoder = New-Object System.Text.ASCIIEncoding

41: $in_quotes = 0

42:  

43: foreach ($c in $to_repair) {

44: # toggle in-quotes flag

45: if ($c -eq 34) {

46: $in_quotes = !$in_quotes;

47: $process_yes_no = 1

48: }

49:  

50: # replace comma in quotes with ~

51: if ($c -eq 44 -and $in_quotes) {

52: foreach ($b in $byteEncoder.GetBytes("~")) {

53: [void]$fixed.Add($b);

54: }

55: continue;

56: }

57:  

58: # pass through the bytes

59: [void]$fixed.Add($c);

60: }

61:  

62: if ($process_yes_no -eq 1 ) {

63: # write-out to fixed csv

64: set-content -encoding byte -path $fixed_file -value $fixed

65:  

66: # carry on with the commas replacements

67: (Get-Content $fixed_file) |

68: Foreach-Object {$_ -replace ",", "|"} |

69: Set-Content $fixed_file

70:  

71: # next do ~ replacements

72: (Get-Content $fixed_file) |

73: Foreach-Object {$_ -replace "~", ","} |

74: Set-Content $fixed_file

75:  

76: # last fix step " removals

77: (Get-Content $fixed_file) |

78: Foreach-Object {$_ -replace """", ""} |

79: Set-Content $fixed_file

80:  

81: # Swap the files

82: Move-Item $fixed_file $InputCSV -Force -ErrorAction SilentlyContinue

83:  

84: #$File_Name = split-path $InputCSV -Leaf # in case the file name needs to be captured

85: }

86: else {

87: Write-Host "No processing was necessary."

88: }

89: }}

90: finally {

91: $reader.Close()

92: }

93:  

94: Write-Host "Processing complete." -BackgroundColor DarkGreen -ForegroundColor yellow

This article is part of the GWB Archives. Original Author: Compudicted

New on Geeks with Blogs

  • We Won The One Award I Actually Care About

    Full Scale made the Inc. 5000 for the fifth year straight, the 12th listing across my three companies. Here is why the one award you cannot buy is worth stopping for.

  • Your Customers Build the Features Now

    I let a tool I liked sit dead for a year rather than build the features I wanted. An MCP server meant I never had to, and your customers can do the same to your product.

  • Get the Size of a Directory in Linux the Easy Way

    du -sh for the quick answer, ncdu for the cleanup, df for the disk itself: every command for checking directory size in Linux, plus why du and df never agree.

  • Vim Search and Replace: The Ultimate Guide

    One :%s command replaces every match in a file before a find dialog would even open. The Vim substitute patterns worth the muscle memory: flags, ranges, capture groups, and multi-file edits.