фонЧтение из трубопровода потока в PowerShell
Я надеюсь писать код, который использует Microsoft.VisualBasic.FileIO.TextFieldParser
для синтаксического анализа некоторых данных в формате CSV. Система, в которой я генерирую эти данные, не понимает кавычек; поэтому я не могу избежать разделителя; а скорее заменить его. Я нашел решение, используя вышеприведенный текстовый синтаксический анализатор, но я видел, как люди его используют с помощью ввода из файлов. Вместо того, чтобы записывать свои данные в файл только для его повторного импорта, я предпочитаю хранить вещи в памяти/использовать конструктор этого класса, который принимает поток в качестве входных данных.
В идеале, он мог бы получать канал напрямую из любого используемого для конвейера потока памяти; но я не мог понять, как получить доступ к этому. В моем текущем коде я создаю свой собственный поток памяти и подаю данные на него из конвейера; затем попытайтесь прочитать это. К сожалению, я что-то пропустил.
Вопросы
- Как вы читаете из/записи в потоки памяти в PowerShell?
- Можно ли читать непосредственно из потока, который подается в конвейер функции?
Код
clear-host
[Reflection.Assembly]::LoadWithPartialName("System.IO") | out-null
#[Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic") | out-null
function Clean-CsvStream {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline=$true)]
[string]$Line
,
[Parameter(Mandatory = $false)]
[char]$Delimiter = ','
)
begin {
[System.IO.MemoryStream]$memStream = New-Object System.IO.MemoryStream
[System.IO.StreamWriter]$writeStream = New-Object System.IO.StreamWriter($memStream)
[System.IO.StreamReader]$readStream = New-Object System.IO.StreamReader($memStream)
#[Microsoft.VisualBasic.FileIO.TextFieldParser]$Parser = new-object Microsoft.VisualBasic.FileIO.TextFieldParser($memStream)
#$Parser.SetDelimiters($Delimiter)
#$Parser.HasFieldsEnclosedInQuotes = $true
#$writeStream.AutoFlush = $true
}
process {
$writeStream.WriteLine($_)
#$writeStream.Flush() #maybe we need to flush it before the reader will see it?
write-output $readStream.ReadLine()
#("Line: {0:000}" -f $Parser.LineNumber)
#write-output $Parser.ReadFields()
}
end {
#close streams and dispose (dodgy catch all's in case object's disposed before we call Dispose)
#try {$Parser.Close(); $Parser.Dispose()} catch{}
try {$readStream.Close(); $readStream.Dispose()} catch{}
try {$writeStream.Close(); $writeStream.Dispose()} catch{}
try {$memStream.Close(); $memStream.Dispose()} catch{}
}
}
1,2,3,4 | Clean-CsvStream -$Delimiter ';' #nothing like the real data, but I'm not interested in actual CSV cleansing at this point
Обход
В то же время мое решение просто сделать это заменить на объектах недвижимости, а игровая, чем строки CSV.
$cols = $objectArray | Get-Member | ?{$_.MemberType -eq 'NoteProperty'} | select -ExpandProperty name
$objectArray | %{$csvRow =$_; ($cols | %{($csvRow.$_ -replace "[`n,]",':')}) -join ',' }
Update
я понял недостающий код был $memStream.Seek(0, [System.IO.SeekOrigin]::Begin) | out-null;
Однако это не ведет себя полностью, как и ожидалось; т.е. первая строка моего CSV отображается дважды, а другой вывод - в неправильном порядке; поэтому, по-видимому, я неправильно понял, как использовать Seek
.
clear-host
[Reflection.Assembly]::LoadWithPartialName("System.IO") | out-null
[Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic") | out-null
function Clean-CsvStream {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline=$true)]
[string]$CsvRow
,
[Parameter(Mandatory = $false)]
[char]$Delimiter = ','
,
[Parameter(Mandatory = $false)]
[regex]$InvalidCharRegex
,
[Parameter(Mandatory = $false)]
[string]$ReplacementString
)
begin {
[System.IO.MemoryStream]$memStream = New-Object System.IO.MemoryStream
[System.IO.StreamWriter]$writeStream = New-Object System.IO.StreamWriter($memStream)
[Microsoft.VisualBasic.FileIO.TextFieldParser]$Parser = new-object Microsoft.VisualBasic.FileIO.TextFieldParser($memStream)
$Parser.SetDelimiters($Delimiter)
$Parser.HasFieldsEnclosedInQuotes = $true
$writeStream.AutoFlush = $true
}
process {
if ($InvalidCharRegex) {
$writeStream.WriteLine($CsvRow)
#flush here if not auto
$memStream.Seek(0, [System.IO.SeekOrigin]::Begin) | out-null;
write-output (($Parser.ReadFields() | %{$_ -replace $InvalidCharRegex,$ReplacementString }) -join $Delimiter)
} else { #if we're not replacing anything, keep it simple
$CsvRow
}
}
end {
"end {"
try {$Parser.Close(); $Parser.Dispose()} catch{}
try {$writeStream.Close(); $writeStream.Dispose()} catch{}
try {$memStream.Close(); $memStream.Dispose()} catch{}
"} #end"
}
}
$csv = @(
(new-object -TypeName PSCustomObject -Property @{A="this is regular text";B="nothing to see here";C="all should be good"})
,(new-object -TypeName PSCustomObject -Property @{A="this is regular text2";B="what the`nLine break!";C="all should be good2"})
,(new-object -TypeName PSCustomObject -Property @{A="this is regular text3";B="ooh`r`nwindows line break!";C="all should be good3"})
,(new-object -TypeName PSCustomObject -Property @{A="this is regular text4";B="I've got;a semi";C="all should be good4"})
,(new-object -TypeName PSCustomObject -Property @{A="this is regular text5";B="""You're Joking!"" said the Developer`r`n""No honestly; it's all about the secret VB library"" responded the Google search result";C="all should be good5"})
) | convertto-csv -Delimiter ';' -NoTypeInformation
$csv | Clean-CsvStream -Delimiter ';' -InvalidCharRegex "[`r`n;]" -ReplacementString ':'
Это может быть именно то, что мне нужно, но не знакомы с сценариями PowerShell. Я не знаю, как использовать код. Если у меня есть файл csv в следующем месте D: \ test \ file to clean.csv, то как мне запустить скрипт? Разделитель - это труба "|" , спецификатор текста не имеет значения, и после каждой строки есть CR. Некоторые поля имеют CR где-то посередине, и этот CR необходимо удалить и заменить пробелом ... –
@ M.R. Если вас все еще интересует; см. обновленный раздел в конце этого ответа. К сожалению, я пропустил этот комментарий изначально, как было, когда вы прокомментировали. – JohnLBevan
Спасибо, Джон, да, меня все еще интересует сценарий, я попробую! –