PowerShell: запись-вывод записывает только один объект

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

Итак, я принимаю совет и стараюсь избегать использования write-host. Одно предложение, которое я нашел, состояло в том, чтобы использовать запись-вывод вместо этого. Насколько я понимаю, это помещает все в конвейер, и вывод выполняется в конце сценария (?).

Однако у меня проблемы с выводом того, что я хочу. Этот пример демонстрирует проблему:

$properties = @{'OSBuild'="910ef01.2.8779";
                'OSVersion'="CustomOS 3";
                'BIOSSerial'="A5FT-XT2H-5A4B-X9NM"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-output $object

$properties = @{'Site'="SQL site";
                'Server'="SQL Server";
                'Database'="SQL Database"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-Output $object

Таким образом, я получаю хороший вывод первого объекта, отображающего данные ОС, но второй объект, содержащий данные SQL, никогда не отображается. Я пытался переименовать имена переменных и кучу других вещей, но не повезло.

При устранении этой проблемы я обнаружил похожие проблемы с предложениями просто заменить запись-вывод на запись-хост. Это меня очень смущает. Почему некоторые люди категорически не рекомендуют write-host, а другие поощряют это?

И как именно вывести эти два объекта модным способом? Я не совсем понимаю конвейерный механизм записи-вывода.

3 ответа

Решение
  • Просто чтобы уточнить: проблема только в отображении проблемы:

    • При выводе на консоль, если первый объект отформатирован в виде таблицы (если Format-Table применяется, что происходит неявно в вашем случае), столбцы дисплея заблокированы на основе свойств этого первого объекта.
      Поскольку ваш второй объект вывода не имеет общих свойств с первым, он ничего не вносит в отображение таблицы и поэтому фактически невидим.
    • Напротив, если вы программно обрабатываете выходные данные сценария - присваиваете его переменной или отправляете его результаты через конвейер другой команде - оба объекта будут там.
      • См . Ответ Чарли Джойнта для полезного примера назначения двух выходных объектов отдельным переменным.
  • Самым простым решением проблемы отображения является явное форматирование отображения каждого входного объекта в отдельности - см. Ниже.


Для данного отдельного объекта внутри скрипта вы можете принудительно отформатировать вывод для отображения (на хост) с помощью Out-Host:

$object | Out-Host # same as: Write-Output $object | Out-Host

Обратите внимание, однако, что это выводит напрямую и неизменно только на консоль, и тогда объект не является частью вывода данных скрипта (объекты, записанные в поток вывода успеха, поток с индексом 1).
Другими словами: если вы попытаетесь присвоить вывод сценария переменной или отправить его вывод другой команде в конвейере, этого объекта там не будет.

Смотрите ниже, почему Out-Host предпочтительнее Write-Host и почему лучше избегать Write-Host в большинстве ситуаций.

Чтобы применить метод ad hoc к выходу данного скрипта в целом, чтобы убедиться, что вы видите все выходные объекты, используйте:

./some-script.ps1 | % { $_ | Out-String }  # % is the built-in alias of ForEach-Object

Обратите внимание, что здесь вы также можете использовать Out-Host, но преимущество использования Out-String является то, что он по-прежнему позволяет захватывать представление для отображения в файле, если это необходимо.

Вот простая вспомогательная функция (фильтр), которую вы можете поместить в свой $PROFILE:

# Once defined, you can use: ./some-script.ps1 | Format-Each
Filter Format-Each { $_ | Out-String }

PetSerAl - ./some-script.ps1 | Format-List - в принципе тоже работает, но переключает вывод с обычного вывода в табличном стиле на вывод в виде списка, причем каждое свойство отображается в отдельной строке, что может быть нежелательно.
Наоборот, однако, Format-Each Если выходной объект (неявно) отформатирован в виде таблицы, печатает заголовок для каждого объекта.


Зачем Write-Output не помогает

Write-Output не помогает, потому что в любом случае записывает туда, куда выводятся объекты вывода: вышеупомянутый поток вывода успеха, куда должны идти данные.

Если объекты выходного потока не перенаправляются или не захватываются в какой-либо форме, они отправляются на хост по умолчанию (обычно на консоль), где применяется автоматическое форматирование.

Кроме того, использование Write-Output редко требуется, потому что просто не захватывает или не перенаправляет команду или выражение, неявно записывает в поток успеха; другой способ выразить это:
Write-Output подразумевается.

Следовательно, следующие два утверждения эквивалентны:

Write-Output $object  # write $object to the success output stream
$object               # same; *implicitly* writes $object to the success output stream

Зачем использовать Write-Host опрометчиво, как здесь, так и часто в целом:

Предполагая, что вы знаете последствия использования Write-Host в общем - см. ниже - вы можете использовать его для решения проблемы, но Write-Host применяется просто .ToString() форматирование на вход, что не дает вам приятного многострочного форматирования, которое PowerShell применяет по умолчанию.
Таким образом, Out-Host (а также Out-String) были использованы выше, потому что они применяют одинаковое, дружественное форматирование.

Сравните следующие два оператора, которые печатают литерал хеш-таблицы ([hashtable]):

# (Optional) use of Write-Output: The friendly, multi-line default formatting is used.
# ... | Out-Host and ... | Out-String would print the same.
PS> Write-Output @{ foo = 1; bar = 'baz' }

Name                           Value
----                           -----
bar                            baz
foo                            1

# Write-Host: The hashtable's *entries* are *individually* stringified
#             and the result prints straight to the console.
PS> Write-Host @{ foo = 1; bar = 'baz' }

System.Collections.DictionaryEntry System.Collections.DictionaryEntry    

Write-Host сделал две вещи здесь, что привело к почти бесполезному выводу:

  • [hashtable] Записи экземпляра были перечислены, и каждая запись была индивидуально строковой.

  • .ToString() Стрификация записей хеш-таблицы (пары ключ-значение) System.Collections.DictionaryEntry т.е. просто имя типа экземпляра.

Основные причины избегания Write-Host в общем есть:

  • Он выводится непосредственно на хост (консоль), а не на поток вывода успешных результатов PowerShell.

    • Как новичок, вы можете ошибочно думать, что Write-Host для записи результатов (данных), но это не так.
  • Обходя систему потоков PowerShell, Write-Host вывод не может быть перенаправлен - то есть он не может быть ни подавлен, ни перехвачен (в файле или переменной).

    • Начиная с PowerShell v5.0, теперь вы можете перенаправить вывод через недавно введенный информационный поток (номер 6; например, ./some-script.ps1 6>write-host-output.txt); тем не менее, этот поток более правильно используется с новым Write-Information Командлет.
      В отличие от Out-Host выход по-прежнему не может быть перенаправлен.

Это оставляет только два законных использования Write-Host:

  • Если вам нужно получить цветной вывод, например, для более удобного интерактивного ввода и упрощения визуального анализа текста для отображения с помощью выборочной раскраски, используйте Write-Host "s -ForegroundColor а также -BackgroundColor параметры.

  • Если вам нужен быстрый и грязный способ записи информации о состоянии / диагностики непосредственно на консоль, не мешая выводу данных скрипта.

    • Тем не менее, как правило, лучше использовать Write-Verbose а также Write-Debug в таких случаях.

Вообще говоря, ожидается, что скрипт / функции вернут один "тип" объекта, часто со многими экземплярами. Например, Get-Process возвращает загрузку процессов, но все они имеют одинаковые поля. Как вы увидели из учебников и т. Д., Вы можете передать результат Get-Process вдоль конвейера и обработки данных с последующими командлетами.

В вашем случае вы возвращаете два разных типа объекта (т.е. с двумя разными наборами свойств). PS выводит первый объект, но не второй (который не соответствует первому), как вы обнаружили. Если бы вы добавили к первому объекту дополнительные свойства, совпадающие с теми, которые использовались во втором, вы бы увидели оба объекта.

Write-Host не волнует такого рода вещи. Отказ от использования этого в основном связан с тем, что (1) это ленивый способ дать отзыв о прогрессе скрипта, то есть использовать Write-Verbose или же Write-Debug вместо этого и (2) он несовершенен, когда речь идет о прохождении объектов по конвейеру и т. д.

Разъяснение по пункту (2), полезно сформулированное в комментариях к этому ответу:

Write-Host не просто несовершенен в отношении захвата конвейера / перенаправления / вывода, вы просто не можете использовать его для этого в PSv4 и более ранних версиях, а в PSv5+ вам приходится неловко использовать 6>; также, Write-Host Стригирует с .ToString(), который часто производит бесполезные представления

Если ваш сценарий действительно предназначен для печати данных на консоль, тогда продолжайте и Write-Host,

Кроме того, вы можете вернуть несколько объектов из скрипта или функции. С помощью return или же Write-Output, просто вернуть объекты объекта через запятую. Например:

Тест-WriteOutput.ps1

$object1 = [PSCustomObject]@{
  OSBuild = "910ef01.2.8779"
  OSVersion = "CustomOS 3"
  BIOSSerial = "A5FT-XT2H-5A4B-X9NM"
}

$object2 = [PSCustomObject]@{
  Site = "SQL site"
  Server= "SQL Server"
  Database="SQL Database"
}

Write-Output $object1,$object2

Запустите скрипт, назначив вывод двум переменным:

$a,$b = .\Test-WriteOutput.ps1

Вы увидите, что $a - это $object1, а $b - это $ object2.

Используйте write-host, write-output для конвейера (и по умолчанию в консоли после очистки)

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