Out-File от Powershell добавляет новую строку в начало файла - Out-File против Set-Content
У меня есть следующий PowerShell:
# Find all .csproj files
$csProjFiles = get-childitem ./ -include *.csproj -recurse
# Remove the packages.config include from the csproj files.
$csProjFiles | foreach ($_) {(get-content $_) |
select-string -pattern '<None Include="packages.config" />' -notmatch |
Out-File $_ -force}
И, кажется, работает нормально. Строка с packages.config отсутствует в файле после запуска.
Но после того, как я бегу, в верхней части файла появляется дополнительная новая строка. (Не дно.)
Я не понимаю, как это происходит. Что я могу сделать, чтобы избавиться от дополнительного символа новой строки, который он генерирует вверху файла?
ОБНОВИТЬ:
Я поменял местами другой способ сделать это:
$csProjFiles | foreach ($_) {$currentFile = $_; (get-content $_) |
Where-Object {$_ -notmatch '<None Include="packages.config" />'} |
Set-Content $currentFile -force}
Он отлично работает и не имеет лишней строки в верхней части файла. Но я не прочь узнать, почему в верхнем примере была добавлена дополнительная строка.
1 ответ
Out-File
и операторы перенаправления>
/>>
взять произвольные входные объекты и преобразовать их в строковые представления, как они будут представлены в консоли, то есть применяется форматирование вывода по умолчанию в PowerShell, и отправить эти строковые представления в выходной файл.
Эти строковые представления часто имеют начальные и / или завершающие символы новой строки для удобства чтения.- Увидеть
Get-Help about_Format.ps1xml
Узнать больше.
- Увидеть
Set-Content
для входных объектов , которые уже являются строками или должны рассматриваться как строки.- Вызовы PowerShell
.psobject.ToString()
на всех входных объектах для получения строкового представления, которое в большинстве случаев относится к базовому типу.NET.ToString()
метод.
- Вызовы PowerShell
Результирующие представления обычно не совпадают, и важно знать, когда выбирать, какой командлет / оператор.
Кроме того, кодировки символов по умолчанию отличаются:
Out-File
а также>
/>>
по умолчанию UTF-16 LE, который вызывает PowerShellUnicode
в контексте факультативного-Encoding
параметр.Set-Content
по умолчанию используется устаревшая кодовая страница "ANSI" вашей системы (однобайтовая кодовая страница с расширенной кодировкой ASCII), которую вызывает PowerShellDefault
,- Обратите внимание, что документы PSv5.1 ошибочно утверждают, что по умолчанию используется ASCII. [1]
Чтобы изменить кодировку:
Специальное изменение: используйте
-Encoding
параметр сOut-File
или жеSet-Content
явно контролировать выходную кодировку символов.
Вы не можете изменить кодировку, используемую>
/>>
ad-hoc, но смотрите ниже.[PSv3 +] Изменение значения по умолчанию (используйте с осторожностью): используйте
$PSDefaultParameterValues
механизм (см.Get-Help about_Parameters_DefaultValues
), что позволяет устанавливать значения по умолчанию для параметров:Изменение кодировки по умолчанию для
Out-File
также меняет его для>
/>>
в PSv5.1 или выше [2].
Например, чтобы изменить его на UTF-8, используйте:$PSDefaultParameterValues['Out-File:Encoding']='UTF8'
- Обратите внимание, что в PSv5.0 или ниже вы не можете изменить кодировку
>
а также>>
использовать.
- Обратите внимание, что в PSv5.0 или ниже вы не можете изменить кодировку
Если вы измените значение по умолчанию для
Set-Content
, обязательно поменяйте его наAdd-Content
тоже:$PSDefaultParameterValues['Set-Content:Encoding'] = $PSDefaultParameterValues['Add-Content:Encoding'] ='UTF8'
Вы также можете использовать шаблоны с подстановочными знаками для представления имени командлета / расширенной функции, к которому применяется значение параметра по умолчанию; например, если вы использовали
$PSDefaultParameterValues['*:Encoding']='UTF8'
то все командлеты, которые имеют-Encoding
параметр по умолчанию будет иметь это значение, но это не рекомендуется, потому что в некоторых командлетах-Encoding
относится к входной кодировке.Среди командлетов, которые пишут в файлы, нет единого общего префикса, позволяющего настроить таргетинг на все выходные командлеты, но вы можете определить шаблон для каждого из глаголов:
$enc = 'UTF8; $PSDefaultParameterValues += @{ 'Out-*:Encoding'=$enc; 'Set-*:Encoding'=$enc; 'Add-*:Encoding'=$enc; 'Export-*:Encoding'=$enc }
Предостережение:
$PSDefaultParameterValues
определяется в глобальной области видимости, поэтому любые изменения, внесенные в него, вступают в силу глобально и влияют на последующие команды.
Чтобы ограничить изменения области видимости скрипта / функции и ее дочерних областей, используйте локальную$PSDefaultParameterValues
переменная, которую можно инициализировать пустой хеш-таблицей, чтобы начать с нуля ($PSDefaultParameterValues = @{}
) или инициализировать клоном глобального значения ($PSDefaultParameterValues = $PSDefaultParameterValues.Clone()
)
В данном случае выходные объекты [Microsoft.PowerShell.Commands.MatchInfo]
экземпляры выводятся Select-String
:
Использование форматирования по умолчанию, как это происходит с
Out-File
, они выводят пустую строку выше и две пустые строки ниже (с множеством экземпляров, печатаемых в непрерывном блоке между одним набором пустых строк выше и ниже).Если вы позвоните
.psobject.ToString()
на них, как это происходит сSet-File
они оценивают только совпадающие строки (без префикса origin-path, учитывая, что ввод был предоставлен через конвейер, а не как имена файлов через-Path
/-LiteralPath
параметры), без начальных или конечных пустых строк.
Тем не менее, если бы вы передали | Select-Object -ExpandProperty Line
или просто | ForEach-Object Line
чтобы явно выводить только совпадающие строки в виде строк, оба Out-File
а также Set-Content
дал бы тот же результат (за исключением их кодировки по умолчанию).
PS: наблюдение LotPing верно: вы, кажется, путаете foreach
заявление с ForEach-Object
Командлет (который, к сожалению, также известен по встроенному псевдониму foreach
, вызывая замешательство).
ForEach-Object
Командлету не нужно явное определение для $_
: в (подразумевается -Process
) блок скрипта, который вы передаете ему, $_
автоматически определяется как входной объект под рукой.
Ваш ($_)
аргумент foreach
(ForEach-Object
) эффективно игнорируется: потому что он оценивает $null
: автоматическая переменная $_
при использовании вне специальных контекстов - таких как блоки скриптов в конвейере - эффективно оценивает $null
и положить (...)
вокруг это не имеет значения, так что вы эффективно проходите $null
, который игнорируется.
[1] Убедитесь, что ASCII
не по умолчанию следующим образом: '0x{0:x}' -f $('ä' | Set-Content t.txt; $b=[System.IO.File]::ReadAllBytes("$PWD\t.txt")[0]; ri t.txt; $b)
доходность 0xe4
в системе en-US, которая является кодовой точкой Windows-1252 для ä
(что совпадает с кодовой точкой Unicode, но вывод представляет собой однобайтовый файл без спецификации).
Если вы используете -Encoding ASCII
явно, вы получаете 0x3f
кодовая точка для литерала ?
, потому что это то, что с помощью ASCII
конвертирует все не-ASCII-символы к.
[2] PetSerAl обнаружил местоположение исходного кода, которое показывает, что >
а также >>
эффективные псевдонимы для Out-File [-Append]
и он указывает, что переопределение Out-File
поэтому также переопределяет >
/ >>
; аналогично, указав кодировку по умолчанию через $PSDefaultParameterValues
за Out-File
также вступает в силу для >
/ >>
,
Windows PowerShell v5.1 является минимальной версией, которая работает таким образом.
Кончик шляпы PetSerAl за помощь.