Powershell: archivo nulo siempre generado (salida de Compare-Object)

La respuesta más popular para esta pregunta implica el siguiente código de Windows PowerShell (editado para corregir un error):

$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 

Siempre obtengo un archivo de cero bytes como salida, incluso si elimino la línea $ Diff.

¿Por qué el archivo de salida siempre es nulo y cómo se puede arreglar?

PetSerAl , como lo hace rutinariamente, ha proporcionado el puntero crucial en un comentario sobre la pregunta (sin intención de convertirlo en una respuesta):

Enumeración de miembros : la capacidad de acceder a un miembro (una propiedad o un método) en una colección y aplicarla implícitamente a cada uno de sus elementos , con los resultados obtenidos en una matriz , se introdujo en PSv3 .

La enumeración de miembros no solo es expresiva y conveniente , sino que también es más rápida que los enfoques alternativos .

Un ejemplo simplificado:

 PS> ((Get-Item /), (Get-Item $HOME)).Mode d--hs- # The value of (Get-Item /).Mode d----- # The value of (Get-Item $HOME).Mode 

Aplicando .Mode a la colección que las salidas del comando (...) hacen que se acceda a la propiedad .Mode en cada elemento de la colección , con los valores resultantes devueltos como una matriz (una matriz PowerShell normal, de tipo [System.Object[]] ).

Advertencias : la enumeración miembro maneja la matriz resultante como lo hace la tubería , lo que significa:

  • Si la matriz tiene solo un elemento, el valor de propiedad de ese elemento se devuelve directamente , no dentro de una matriz de un solo elemento:

     PS> @([pscustomobject] @{foo=1}).foo.GetType().Name Int32 # 1 was returned as a scalar, not as a single-element array. 
  • Si los valores de propiedad que se recostackn son en sí mismos matrices, se devuelve una matriz plana de valores:

     PS> @([pscustomobject] @{foo=1,2}, [pscustomobject] @{foo=3,4}).foo.Count 4 # a single, flat array was returned: 1, 2, 3, 4 

Además, la enumeración de miembros solo funciona para obtener (leer) valores de propiedad , no para establecerlos (escribirlos). Esta asimetría es por diseño , para evitar modificaciones masivas potencialmente no deseadas; en PSv4 +, use .ForEach(') como la solución más rápida (vea más abajo).


Esta práctica función NO está disponible , sin embargo:

  • si está ejecutando en PSv2 (categóricamente)
  • si la colección en sí tiene un miembro con el nombre especificado , en cuyo caso se aplica el miembro de nivel de colección.

Por ejemplo, incluso en PSv3 + lo siguiente NO realiza la enumeración de miembros:

  PS> ('abc', 'cdefg').Length # Try to report the string lengths 2 # !! The *array's* .Length property value (item count) is reported, not the items' 

En tales casos, y en PSv2 en general, se necesita un enfoque diferente:

  • La alternativa más rápida , utilizando la statement foreach , suponiendo que toda la colección se ajusta a la memoria como un todo (lo cual está implícito cuando se usa la enumeración de miembros).
 PS> foreach ($s in 'abc', 'cdefg') { $s.Length } 3 5 
  • Alternativa PSv4 + , utilizando el método de recostackción .ForEach() , que también opera en la colección como un todo:
 PS> ('abc', 'cdefg').ForEach('Length') 3 5 

Nota: Si corresponde a la colección de entrada, también puede establecer valores de propiedad con .ForEach('', ) , que es la solución más rápida para no poder usar . = , es decir, la imposibilidad de establecer valores de propiedad con la enumeración de miembros.

  • Los enfoques más lentos, pero eficientes desde el punto de vista de la memoria , que usan el pipeline :

Nota: El uso de la tubería solo es eficiente desde el punto de vista de la memoria si procesa los elementos uno por uno, de forma aislada, sin recostackr también los resultados en la memoria.

Usando el cmdlet ForEach-Object , como en la útil respuesta de Burt Harris :

 PS> 'abc', 'cdefg' | ForEach-Object { $_.Length } 3 5 

Solo para propiedades (a diferencia de los métodos), Select-Object -ExpandProperty es una opción; es conceptualmente claro y simple, y prácticamente a la par con el enfoque ForEach-Object en términos de rendimiento (para una comparación de rendimiento, vea la última sección de esta respuesta mía):

 PS> 'abc', 'cdefg' | Select-Object -ExpandProperty Length 3 5 

Quizás en lugar de

 $LeftSide = ($Diff | Where-Object {$_.SideIndicator -eq '<='}).InputObject 

PowerShell 2 podría funcionar mejor con:

 $LeftSide = $Diff | Where-Object {$_.SideIndicator -eq '<='} | Foreach-object { $_.InputObject }