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 } 
Другие вопросы по тегам