Я использую Powershell для некоторой работы ETL, читая сжатые текстовые файлы в и разделяя их в зависимости от первых трех символов каждой строки.
Если бы я просто фильтровал входной файл, то я мог бы передать фильтрованный поток по каналу в-Файл и сделан с ним. Но я должен перенаправить вывод больше чем одному месту назначения, и насколько я знаю, что это не может быть сделано с простым каналом. Я уже использую.NET streamreader для чтения сжатых входных файлов, и я задаюсь вопросом, должен ли я использовать streamwriter для записи выходных файлов также.
Наивная версия выглядит примерно так:
while (!$reader.EndOfFile) {
$line = $reader.ReadLine();
switch ($line.substring(0,3) {
"001" {Add-Content "output001.txt" $line}
"002" {Add-Content "output002.txt" $line}
"003" {Add-Content "output003.txt" $line}
}
}
Это просто похоже на плохие новости: открытие, открытие, запись и закрытие файла однажды на строку. Входные файлы составляют огромные 500 МБ + монстры.
Существует ли идиоматический способ обработать это эффективно w/конструкции Powershell, или я должен обратиться к.NET streamwriter?
Есть ли методы (Новый объект "путь" - вводят "файл"), возражают, что я мог использовать для этого?
РЕДАКТИРОВАНИЕ для контекста:
Я пользуюсь библиотекой DotNetZip для чтения zip-файлов как потоков; таким образом streamreader
вместо Get-Content
/gc
. Пример кода:
[System.Reflection.Assembly]::LoadFrom("\Path\To\Ionic.Zip.dll")
$zipfile = [Ionic.Zip.ZipFile]::Read("\Path\To\File.zip")
foreach ($entry in $zipfile) {
$reader = new-object system.io.streamreader $entry.OpenReader();
while (!$reader.EndOfFile) {
$line = $reader.ReadLine();
#do something here
}
}
Я должен, вероятно, Dispose()
и $zipfile и $reader, но это для другого вопроса!
Как и для чтения файла и анализа, я бы пошел с выключателем
:
switch -file c:\temp\stackoverflow.testfile2.txt -regex {
"^001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $_}
"^002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $_}
"^003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $_}
}
Я думаю, что это лучше подходит, потому что
-File
довольно удобно;) Как для написания вывода, я проверю, чтобы использовать Streamwriter, однако, если производительность Add-Content
достойно Вы, я бы придерживался этого.
Добавлено:
Кит предложил использовать оператора >>
, однако, кажется, оно очень медленно. Кроме того, он записывает вывод в Unicode, который удваивает размер файла.
Посмотрите на мой тест:
[1]: (measure-command {
>> gc c:\temp\stackoverflow.testfile2.txt | %{$c = $_; switch ($_.Substring(0,3)) {
>> '001'{$c >> c:\temp\stackoverflow.testfile.001.txt} `
>> '002'{$c >> c:\temp\stackoverflow.testfile.002.txt} `
>> '003'{$c >> c:\temp\stackoverflow.testfile.003.txt}}}
>> }).TotalSeconds
>>
159,1585874
[2]: (measure-command {
>> gc c:\temp\stackoverflow.testfile2.txt | %{$c = $_; switch ($_.Substring(0,3)) {
>> '001'{$c | Add-content c:\temp\stackoverflow.testfile.001.txt} `
>> '002'{$c | Add-content c:\temp\stackoverflow.testfile.002.txt} `
>> '003'{$c | Add-content c:\temp\stackoverflow.testfile.003.txt}}}
>> }).TotalSeconds
>>
9,2696923
Разница Огромная .
Просто для сравнения:
[3]: (measure-command {
>> $reader = new-object io.streamreader c:\temp\stackoverflow.testfile2.txt
>> while (!$reader.EndOfStream) {
>> $line = $reader.ReadLine();
>> switch ($line.substring(0,3)) {
>> "001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $line}
>> "002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $line}
>> "003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $line}
>> }
>> }
>> $reader.close()
>> }).TotalSeconds
>>
8,2454369
[4]: (measure-command {
>> switch -file c:\temp\stackoverflow.testfile2.txt -regex {
>> "^001" {Add-Content c:\temp\stackoverflow.testfile.001.txt $_}
>> "^002" {Add-Content c:\temp\stackoverflow.testfile.002.txt $_}
>> "^003" {Add-Content c:\temp\stackoverflow.testfile.003.txt $_}
>> }
>> }).TotalSeconds
8,6755565
Добавлено: Мне было интересно производительность письма .. И я был немного удивлен
[8]: (measure-command {
>> $sw1 = new-object io.streamwriter c:\temp\stackoverflow.testfile.001.txt3b
>> $sw2 = new-object io.streamwriter c:\temp\stackoverflow.testfile.002.txt3b
>> $sw3 = new-object io.streamwriter c:\temp\stackoverflow.testfile.003.txt3b
>> switch -file c:\temp\stackoverflow.testfile2.txt -regex {
>> "^001" {$sw1.WriteLine($_)}
>> "^002" {$sw2.WriteLine($_)}
>> "^003" {$sw3.WriteLine($_)}
>> }
>> $sw1.Close()
>> $sw2.Close()
>> $sw3.Close()
>>
>> }).TotalSeconds
>>
0,1062315
, это в 80 раз быстрее .
Теперь вы должны решить - если бы скорость важна, используйте Streamwriter
. Если кодовая ясность важна, используйте Add-Content
.
в соответствии с подстрокой Кейта на 20% быстрее. Это зависит, как всегда. Однако в моем случае результаты такие:
[102]: (measure-command {
>> gc c:\temp\stackoverflow.testfile2.txt | %{$c = $_; switch ($_.Substring(0,3)) {
>> '001'{$c | Add-content c:\temp\stackoverflow.testfile.001.s.txt} `
>> '002'{$c | Add-content c:\temp\stackoverflow.testfile.002.s.txt} `
>> '003'{$c | Add-content c:\temp\stackoverflow.testfile.003.s.txt}}}
>> }).TotalSeconds
>>
9,0654496
[103]: (measure-command {
>> gc c:\temp\stackoverflow.testfile2.txt | %{$c = $_; switch -regex ($_) {
>> '^001'{$c | Add-content c:\temp\stackoverflow.testfile.001.r.txt} `
>> '^002'{$c | Add-content c:\temp\stackoverflow.testfile.002.r.txt} `
>> '^003'{$c | Add-content c:\temp\stackoverflow.testfile.003.r.txt}}}
>> }).TotalSeconds
>>
9,2563681
Так что разница не важна и для меня, Regexes более читаются.
Учитывая размер входных файлов, вы определенно хотите обрабатывать строку за раз. Не думаю, что повторное открытие/закрытие выходных файлов будет слишком большим хитом. Это, безусловно, делает реализацию возможной даже при использовании конвейера в качестве одного лайнера - на самом деле, не слишком отличающегося от вашего имплантата. Я завернул его сюда, чтобы избавиться от горизонтальной полосы прокрутки:
gc foo.log | %{switch ($_.Substring(0,3)) {
'001'{$input | out-file output001.txt -enc ascii -append} `
'002'{$input | out-file output002.txt -enc ascii -append} `
'003'{$input | out-file output003.txt -enc ascii -append}}}