2017-02-06 4 views
1

Я использую .baat для перемещения нескольких файлов в другую папку, но перед фактической частью перемещения я хочу заменить LAST-строку (это известная линия), например, у меня есть файл output.txt, как это:Используя PowerShell в файле .bat, замените строку несколькими строками

HEADER 
    BODY 
FOOTER

Используя этот фрагмент кода:

powershell -Command "(gc output.txt) -replace 'FOOTER', 'ONE_MORE_LINE `r`n FOOTER' | Out-File output.txt" 

возвращение, что я ожидал был

HEADER 
    BODY 
ONE_MORE_LINE 
FOOTER

Но что я был:

HEADER 
    BODY 
ONE_MORE_LINE `r`n FOOTER

Я пробовал:

  • \n
  • <br>
  • "`r`n"
  • "`n"
  • echo ONE_MORE_LINE >> output.txt; echo. >> output.txt; echo FOOTER >> output.txt"

Этот последний был близок, но результатом были некоторые сломанные персонажи.

Другие предложения, кроме PowerShell, приветствуются. Я использую его только потому, что было легко получить добавление линий и заменить его.

EDIT: Пробовал эту команду

powershell -Command "(gc output.txt) -replace 'FOOTER;', ""ONE_MORE_LINE `r`n FOOTER"" | Out-File output.txt " 

И возвращается сообщение об ошибке:

A cadeia de caracteres não tem o terminador: ". 
    + CategoryInfo   : ParserError: (:) [], ParentContainsErrorRecordException 
    + FullyQualifiedErrorId : TerminatorExpectedAtEndOfString

edit2 - возможное решение:

Я понял, что с помощью команды PowerShell изменил кодировку файл, взломав echo ONE_MORE_LINE, и используя предложение от @AnsgarWiechers, я сделал этот код

findstr /v "FOOTER" output.sql > new_output.sql 
TYPE new_output.sql > output.sql 
del new_output.sql 
ECHO. >> %%f 
ECHO ONE_MORE_LINE >> %%f 
ECHO FOOTER >> %%f 
ECHO. >> %%f 

Что она делает это с помощью commant findstr /v «Footer» я смотрю на все линии, которые не FOOTER в файле output.sql и записать его на new_output.sql

Тогда я TYPE его обратно исходный файл и DEL new_output.sql

Тогда я Echo все строки, которые мне нужны прямо под ним.

Это работает, но для больших файлов я думаю, что повторная запись его дважды займет много времени, но я не могу понять другое решение.

+3

PowerShell не расширяет escape-последовательности в одиночных кавычках. Вы должны использовать там строку с двумя кавычками. Дважды вложенные двойные кавычки, чтобы избежать их для CMD: '' powershell -Command '(...) -replace' FOOTER ', "ONE_MORE_LINE' r'n FOOTER "" | ... "' ' –

+1

Вы могли бы сделать это в чистой партии с командой 'FOR/F'. Вы просто держите предыдущую строку в переменной. Когда следующая строка будет прочитана, выпишите предыдущую строку. Когда команда 'FOR' будет выполнена, выпишите свою вторую в последнюю строку, а затем свою последнюю строку, которая будет удерживаться в предыдущей переменной. – Squashman

+0

@AnsgarWiechers - Когда я удвоил вложенные двойные кавычки, он возвратил ошибку, сказав, что цепочка символов не имеет конца. – Websis

ответ

0

При работе с большими файлами лучше всего использовать поток файлов. Более типичные методы чтения файла по строкам с использованием цикла Batch for /f или с использованием Get-Content в PowerShell для чтения всего файла в память могут замедлить процесс сканирования с большими файлами.С другой стороны, используя поток файлов, вы можете почти мгновенно искать назад с конца файла в начале последней строки, вставлять нужные данные и затем собирать записанные вами байты.

Следующий пример будет использовать доступ PowerShell к методам .NET, чтобы открыть файл в виде потока байтов для быстрого чтения и записи. Подробнее см. Встроенные комментарии. Мы надеемся, что кодирование файлов будет сохранено. Сохраните это с помощью расширения .bat и сделайте снимок.

<# : batch portion 
@echo off & setlocal 

set "file=test.txt" 
set "line=Line to insert!" 

powershell -noprofile "iex (${%~f0} | out-string)" 
goto :EOF 
: end batch/begin PowerShell hybrid #> 

# construct a file stream for reading and writing $env:file 
$IOstream = new-object IO.FileStream((gi $env:file).FullName, 
    [IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite) 

# read BOM to determine file encoding 
$reader = new-object IO.StreamReader($IOstream) 
[void]$reader.Read((new-object byte[] 3), 0, 3) 
$encoding = $reader.CurrentEncoding 
$reader.DiscardBufferedData() 

# convert line-to-insert to file's native encoding 
$utf8line = [Text.Encoding]::UTF8.GetBytes("`r`n$env:line") 
$line = [Text.Encoding]::Convert([Text.Encoding]::UTF8, $encoding, $utf8line) 
$charSize = [math]::ceiling($line.length/$utf8line.length) 

# move pointer to the end of the stream 
$pos = $IOstream.Seek(0, [IO.SeekOrigin]::End) 

# walk back pointer while stream returns no error 
while ($char -gt -1) { 
    $IOstream.Position = --$pos 
    $char = $reader.Peek() 
    $reader.DiscardBufferedData() 

    # break out of loop when line feed preceding non-whitespace is found 
    if ($foundPrintable) { if ($char -eq 10) { break } } 
    else { if ([char]$char -match "\S") { $foundPrintable++ } } 
} 

# step pointer back to carriage return and read to end into $buffer with $line prepended 
$pos -= $charSize 
$IOstream.Position = $pos 
$buffer = $encoding.GetBytes($encoding.GetString($line) + $reader.ReadToEnd()) 
$IOStream.Position = $pos 

"Inserting data at byte $pos" 
$IOstream.Write($buffer, 0, $buffer.Length) 

# Garbage collection 
$reader.Dispose() 
$IOstream.Dispose() 

Этот метод должен быть гораздо более эффективным, чем чтение файла с самого начала, или скопировать весь файл в память или на диск с новой строкой вставленной. В моем тестировании он вставляет строку в сто мега файл примерно за 1/3 секунды.

+0

Обратите внимание, что «большие файлы» в этом случае требуют, чтобы несколько GiB были досадно медленными. И даже тогда, если вы просто выполняете базовые замены, вы можете использовать '-ReadCount' для' Get-Content', чтобы немного ускорить его. Я сомневаюсь, что у них на самом деле есть такие большие файлы, что требуется такая сложность. – Joey

+0

@Joey По моему опыту даже разбор 100 мегабайт может занимать больше часа или более. [См. Эту тему комментариев] (http://stackoverflow.com/questions/15628017/regexp-match-within-a-log-file-return-dynamic-content-above-and-below-match#comment22178573_15629509) для одного такой пример, почему я когда-то укушен, дважды стесняюсь, почему я по умолчанию рекомендую парсер для потока для такого рода задач. Мне было бы интересно увидеть, как вы можете реализовать 'gc -ReadCount' для ускорения задачи, если вы хотите отправить ответ. – rojo

+1

Мне недавно пришлось написать сценарий, который взял 10-гигабайтный CSV-файл и заменил в нем строки. Это заняло минуты (досадно), но определенно не часы. '-ReadCount' значительно сократит накладные расходы на конвейер, но может осложнить код после' Get-Content' в конвейере. Это компромисс. Я могу сделать несколько тестов, как только я перестану работать. – Joey

Смежные вопросы