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
выход по-прежнему не может быть перенаправлен.
- Начиная с PowerShell v5.0, теперь вы можете перенаправить вывод через недавно введенный информационный поток (номер
Это оставляет только два законных использования 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 для конвейера (и по умолчанию в консоли после очистки)