Ответ 1
Это не самый эффективный в мире, но это должно работать:
get-content $file |
select -Skip 1 |
set-content "$file-temp"
move "$file-temp" $file -Force
Я пытаюсь просто удалить первую строку из примерно 5000 текстовых файлов, прежде чем импортировать их.
Я все еще очень новичок в PowerShell, поэтому не уверен, что искать и как подойти к этому. Моя текущая концепция с использованием псевдокода:
set-content file (get-content unless line contains amount)
Однако я не могу понять, как сделать что-то вроде содержащего.
Это не самый эффективный в мире, но это должно работать:
get-content $file |
select -Skip 1 |
set-content "$file-temp"
move "$file-temp" $file -Force
В то время как я действительно восхищаюсь ответом от @hoge как для очень краткой техники, так и для функции обертки, чтобы обобщить ее, и я поощряю ее, я вынужден прокомментировать два других ответа, которые используют временные файлы (он грызет мне, как ногти на доске!).
Предполагая, что файл не огромен, вы можете заставить конвейер работать в дискретных разделах - тем самым устраняя необходимость в временном файле - с разумным использованием круглых скобок:
(Get-Content $file | Select-Object -Skip 1) | Set-Content $file
... или в краткой форме:
(gc $file | select -Skip 1) | sc $file
Используя нотацию переменных, вы можете сделать это без временного файла:
${C:\file.txt} = ${C:\file.txt} | select -skip 1
function Remove-Topline ( [string[]]$path, [int]$skip=1 ) {
if ( -not (Test-Path $path -PathType Leaf) ) {
throw "invalid filename"
}
ls $path |
% { iex "`${$($_.fullname)} = `${$($_.fullname)} | select -skip $skip" }
}
Мне просто нужно было выполнить одну и ту же задачу, а gc | select ... | sc
заняла 4-х гигабайт ОЗУ на моей машине, читая файл размером 1,6 фунта. Он не завершился в течение как минимум 20 минут после прочтения всего файла (как сообщается Read Bytes в Process Explorer), при котором Я должен был убить его.
Моим решением было использование более .NET-подхода: StreamReader
+ StreamWriter
.
См. Этот ответ для отличного ответа на обсуждение perf: В Powershell наиболее эффективный способ разделить большой текстовый файл по типу записи?
Ниже мое решение. Да, он использует временный файл, но в моем случае это не имело значения (это было огромное создание таблицы SQL и вставка файла инструкций):
PS> (measure-command{
$i = 0
$ins = New-Object System.IO.StreamReader "in/file/pa.th"
$outs = New-Object System.IO.StreamWriter "out/file/pa.th"
while( !$ins.EndOfStream ) {
$line = $ins.ReadLine();
if( $i -ne 0 ) {
$outs.WriteLine($line);
}
$i = $i+1;
}
$outs.Close();
$ins.Close();
}).TotalSeconds
Он вернул:
188.1224443
Вдохновленный ответом AASoft, я вышел, чтобы улучшить его немного больше:
$i
и сравнения с 0
в каждом циклеtry..finally
, чтобы всегда закрывать используемые файлы$p
для ссылки на текущий каталогЭти изменения приводят к следующему коду:
$p = (Get-Location).Path
(Measure-Command {
# Number of lines to skip
$skip = 1
$ins = New-Object System.IO.StreamReader ($p + "\test.log")
$outs = New-Object System.IO.StreamWriter ($p + "\test-1.log")
try {
# Skip the first N lines, but allow for fewer than N, as well
for( $s = 1; $s -le $skip -and !$ins.EndOfStream; $s++ ) {
$ins.ReadLine()
}
while( !$ins.EndOfStream ) {
$outs.WriteLine( $ins.ReadLine() )
}
}
finally {
$outs.Close()
$ins.Close()
}
}).TotalSeconds
Первое изменение привело к тому, что время обработки моего 60-мегабайтного файла сократилось с 5.3s
до 4s
. Остальные изменения более косметичны.
Я только что узнал на веб-сайте:
Get-ChildItem *.txt | ForEach-Object { (get-Content $_) | Where-Object {(1) -notcontains $_.ReadCount } | Set-Content -path $_ }
Или вы можете использовать псевдонимы, чтобы сделать его коротким, например:
gci *.txt | % { (gc $_) | ? { (1) -notcontains $_.ReadCount } | sc -path $_ }
$x = get-content $file
$x[1..$x.count] | set-content $file
Просто так много. Далее следует длинное скучное объяснение. Get-content возвращает массив. Мы можем "индексировать" в переменные массива, как показано в this и other Сообщения сценариев Guys.
Например, если мы определим такую переменную массива,
$array = @("first item","second item","third item")
поэтому $array возвращает
first item
second item
third item
то мы можем "индексировать" этот массив для извлечения только его 1-го элемента
$array[0]
или только его второй
$array[1]
или диапазон значений индекса со второго по последний.
$array[1..$array.count]
skip` не работает, поэтому мой способ обхода
$LinesCount = $(get-content $file).Count
get-content $file |
select -Last $($LinesCount-1) |
set-content "$file-temp"
move "$file-temp" $file -Force
Другой подход для удаления первой строки из файла, используя технику множественного присваивания. Ссылка Ссылка
$firstLine, $restOfDocument = Get-Content -Path $filename
$modifiedContent = $restOfDocument
$modifiedContent | Out-String | Set-Content $filename
Для небольших файлов вы можете использовать это:
& C:\windows\system32\more +1 oldfile.csv > newfile.csv | из-нуль
... но это не очень эффективно при обработке моего файла примера 16 МБ. Кажется, что он не завершает и не освобождает блокировку newfile.csv.