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() метод.

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

Кроме того, кодировки символов по умолчанию отличаются:

  • Out-File а также > / >> по умолчанию UTF-16 LE, который вызывает PowerShell Unicode в контексте факультативного -Encoding параметр.
  • Set-Content по умолчанию используется устаревшая кодовая страница "ANSI" вашей системы (однобайтовая кодовая страница с расширенной кодировкой ASCII), которую вызывает PowerShell Default,

    • Обратите внимание, что документы 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 или ниже вы не можете изменить кодировку > а также >> использовать.
    • Если вы измените значение по умолчанию для 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 за помощь.

Другие вопросы по тегам