Powershell: всегда генерируется нулевой файл (вывод Compare-Object)
Наиболее популярный ответ на этот вопрос включает следующий код Windows PowerShell (отредактированный для исправления ошибки):
$file1 = Get-Content C:\temp\file1.txt
$file2 = Get-Content C:\temp\file2.txt
$Diff = Compare-Object $File1 $File2
$LeftSide = ($Diff | Where-Object {$_.SideIndicator -eq '<='}).InputObject
$LeftSide | Set-Content C:\temp\file3.txt
Я всегда получаю файл нулевого байта в качестве вывода, даже если я удаляю строку $Diff.
Почему выходной файл всегда нулевой, и как его можно исправить?
2 ответа
PetSerAl, как он обычно делает, предоставил ключевой указатель в комментарии к вопросу (без намерения превратить его в ответ):
Перечисление членов - в PSv3 была представлена возможность доступа к члену (свойству или методу) в коллекции и его неявное применение к каждому из его элементов с получением результатов в массиве.
Перечисление членов не только выразительно и удобно, но и быстрее, чем альтернативные подходы.
Упрощенный пример:
PS> ((Get-Item /), (Get-Item $HOME)).Mode
d--hs- # The value of (Get-Item /).Mode
d----- # The value of (Get-Item $HOME).Mode
применение .Mode
в коллекции, что (...)
-замкнутые выходы команды вызывают .Mode
свойство, доступное для каждого элемента в коллекции, с результирующими значениями, возвращаемыми в виде массива (обычный массив PowerShell, типа [System.Object[]]
).
Предостережения: перечисление членов обрабатывает результирующий массив, как конвейер, что означает:
Если массив имеет только один элемент, значение свойства этого элемента возвращается напрямую, а не внутри одноэлементного массива:
PS> @([pscustomobject] @{foo=1}).foo.GetType().Name Int32 # 1 was returned as a scalar, not as a single-element array.
Если собранные значения свойств сами являются массивами, возвращается плоский массив значений:
PS> @([pscustomobject] @{foo=1,2}, [pscustomobject] @{foo=3,4}).foo.Count 4 # a single, flat array was returned: 1, 2, 3, 4
Кроме того, перечисление членов работает только для получения (чтения) значений свойств, а не для установки (записи) их. Эта асимметрия задуманна, чтобы избежать потенциально нежелательных массовых изменений; в PSv4+ используйте .ForEach('<property-name', <new-value>)
как самый быстрый обходной путь (см. ниже).
Эта удобная функция НЕ доступна, однако:
- если вы работаете на PSv2 (категорически)
- если в самой коллекции есть член с указанным именем, то в этом случае применяется элемент уровня коллекции.
Например, даже в PSv3+ следующее НЕ выполняет перечисление членов:
PS> ('abc', 'cdefg').Length # Try to report the string lengths
2 # !! The *array's* .Length property value (item count) is reported, not the items'
В таких случаях - и в PSv2 в целом - необходим другой подход:
- Самая быстрая альтернатива, используя
foreach
утверждение, предполагая, что вся коллекция помещается в память в целом (что подразумевается при использовании перечисления членов).
PS> foreach ($s in 'abc', 'cdefg') { $s.Length }
3
5
- PSv4+ альтернатива, используя метод сбора
.ForEach()
Также работает над коллекцией в целом:
PS> ('abc', 'cdefg').ForEach('Length')
3
5
Примечание. Если это применимо к входной коллекции, вы также можете установить значения свойств с помощью .ForEach('<prop-name>', <new-value>)
, который является самым быстрым обходным путем, чтобы не быть в состоянии использовать .<prop-name> = <new-value>
т.е. невозможность установить значения свойств с перечислением членов.
- Медленные, но эффективные с точки зрения памяти подходы с использованием конвейера:
Примечание. Использование конвейера эффективно только для памяти, если вы обрабатываете элементы по одному, изолированно, без сбора результатов в памяти.
С использованием ForEach-Object
Командлет, как в полезном ответе Берта Харриса:
PS> 'abc', 'cdefg' | ForEach-Object { $_.Length }
3
5
Только для свойств (в отличие от методов), Select-Object -ExpandProperty
это вариант; это концептуально ясно и просто, и практически наравне с ForEach-Object
подход с точки зрения производительности (для сравнения производительности, см. последний раздел моего ответа):
PS> 'abc', 'cdefg' | Select-Object -ExpandProperty Length
3
5
Возможно вместо
$LeftSide = ($Diff | Where-Object {$_.SideIndicator -eq '<='}).InputObject
PowerShell 2 может работать лучше с:
$LeftSide = $Diff | Where-Object {$_.SideIndicator -eq '<='} |
Foreach-object { $_.InputObject }