Rimuovere una riga da una powershell array/collezione/hashtable

Ho un grande array (importato da un foglio di calcolo di Excel) che ha un “totale” di riga che si desidera eliminare. È sempre l’ultimo elemento dell’array. Ho cercato di trovare un modo per eliminare solo l’ultimo elemento dell’array, ma non può. I dati e il codice di esempio riportato di seguito:

$data = Import-XLSX -Path "C:\Pathtofile\spreadsheet.xlsx" -Sheet "SheetName" -RowStart 3
$data | ? {$_.Server -eq "Total"}


Server        : Total
VLAN          :
IP Address    :
CPU           : 84
RAM           : 313
C:\           : 2840
D:\           : 17950
OS Version    :
OS Edition    :
SQL Version   :
SQL Edition   :
Datastore     :
Reboot        :
Notes         :

Mi piacerebbe essere in grado di eliminare questa riga della matrice. Ho provato le seguenti:

$data.Remove("Total")
Exception calling "Remove" with "1" argument(s): "Collection was of a fixed size."
At line:1 char:1
+ $ImportCSV.Remove("Total")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NotSupportedException

Se posso controllare il tipo di matrice, ottengo il seguente:

$data.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

Se mi riscrivi l’oggetto come ottengo il seguente:

$data = {$data}.Invoke()
$data.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Collection`1                             System.Object

$data.Remove("Total")
False

Questo non cancella la voce. Quello che mi manca qui?

OriginaleL’autore McKenning | 2016-10-27

2 risposte

  1. 15

    Matrici sono immutabili in PowerShell, non è possibile aggiungere o rimuovere elementi da loro. Cosa si può fare è:

    a) creare un nuovo array e filtro fuori quello che non si vuole (ad esempio con -ne):

    $data = $data | ? {$_.Server -ne "Total"}

    o

    b) Selezionare tutto fino all’ultimo elemento di indice (potrebbe essere affamati di risorse su un grande array, non so):

    $data = $data[0..($data.Count-2)]

    o

    c) il Cast a [System.Collections.ArrayList], che è mutevole, quindi rimuovere un elemento dall’:

    $data = [System.Collections.ArrayList]$data
    $data.RemoveAt($data.Count-1)
    Ho provato un metodo di metodo e di tre con successo! Nota che con il “immutabile” la natura della matrice, sono in grado di tagliare un “vuoto” righe con il seguente comando: $data | Where-Object { ($_.PSObject.Proprietà | ForEach-Object { $_.Valore }) -ne $null }. Questo è il motivo per cui ho pensato che avrei potuto semplicemente rimuovere l’ultima voce.
    Il $data | Where-Object ... comando crea un nuova array, come ogni gasdotto che uscite più di 1 oggetto. Anche se si assegna il risultato alla stessa variabile, è ancora un nuovo array, come dimostra questo esempio: $a = 1, 2, 3; $aToo = $a; $a = $a | ? { $_ -ne 2 }; 'new $a:'; $a; 'original $a:'; $aToo.

    OriginaleL’autore TessellatingHeckler

  2. 5

    Per completare TessellatingHeckler utile risposta con l’analisi e le informazioni di sfondo:

    • Come detto, PowerShell (matrici.NET tipo [System.Object[]]) sono dimensione fissa – non è possibile aggiungere o rimuovere elementi (è possibile, tuttavia, modificare il valore di elementi esistenti).

      • Quando si “aggiungi” elementi per una PowerShell array con + o +=, un nuovo array è creato dietro le quinte, che è un copia (di poco clone) dell’originale.

      • L’unico motivo di array di .Add(), .Remove(), e .RemoveAt() metodi è che hanno implementato la general-purpose System.Collections.IList interfaccia, che viene implementato da diversi tipi di collezioni, alcune delle quali sono ridimensionabile.

        Prova a chiamare questi metodi in una dimensione fissa di raccolta come un array di risultati in "Collection was of a fixed size." messaggio di errore che hai visto.

    • Invio nulla attraverso la pipeline che uscite più di 1 oggetto viene fuori come un (nuovo) array, indipendentemente dal tipo di raccolta di input. (Un singolo oggetto risultato è uscita come se stesso, non avvolto in una raccolta.)

      • Quindi, filtrare gli elementi di una collection con comandi come $data | ? {$_.Server -ne "Total"} crea un nuovo collezione che è sempre un array ([System.Object[]], se c’è più di 1 oggetto di output).

    Perché i tuoi tentativi non è riuscito:

    $data.Remove("Total") fallito fondamentalmente, perché gli array non supporta la rimozione, come detto.

    Ma anche se $data erano ridimensionabile raccolta (ad esempio,[System.Collections.ArrayList]), passando "Total" non avrebbe funzionato, perché il IList.Rimuovere metodo provato a chiamare richiede il passaggio di un elemento per rimuovere stesso, che nel tuo caso sarebbe il oggetto cui .Server proprietà contiene "Total", più facilmente accessibile come $data[-1].

    Anche notare che il passaggio di un valore che non è un elemento della lista è un tranquilla no-op (se la raccolta è ridimensionabile).

    Dato che si voleva rimuovere la ultimo elemento, l’indice a base di IList.RemoveAt metodo sarebbe stata la scelta più semplice, tuttavia.


    {$data}.Invoke() infatti esegue un ridigitare del genere, ma in un arcano di moda che è meglio evitare:

    A prima vista, concettualmente, il comando dovrebbe essere un no-op: sei semplicemente allegando un riferimento a una variabile in un blocco di script ({ ... }), che si richiama quindi, che dovrebbe semplicemente uscite della variabile valore.

    Il motivo per cui si non è no-op è che stai bypassando PowerShell normale manipolazione di blocchi di script da richiamare tramite il .NET .Invoke() metodo, invece di come blocchi di script sono normalmente chiamati: con l’operatore di chiamata &.

    Se si utilizza .Invoke() su un blocco di script, è sempre ottenere un [System.Collections.ObjectModel.Collection[psobject]] istanza (anche se contiene solo 1 oggetto).

    Utilizzando invece & funziona aggiuntiva dopo aver invocato .Invoke() dietro le quinte: se la raccolta contiene solo 1 elemento, l’elemento viene restituito direttamente (la raccolta è da scartare). In caso contrario, la collezione è convertito in PowerShell array ([System.Object[]]; sono poco chiare sulle specifiche della conversione).

    Dal momento che hai usato .Invoke() direttamente, la chiamata al .Remove() operato [System.Collections.ObjectModel.Collection[psobject]], che è ridimensionabile,
    e ha il suo .Remove() metodo, la cui rimozione logica è la stessa di IList.Remove(), che ombre.

    Tuttavia, si differenzia da IList.Remove() in che restituisce un [bool] che riflette il successo del tentativo di rimozione, che è il motivo per cui hai preso una false sul 2 ° tentativo.

    A differenza del 1 ° tentativo, tuttavia, questa volta l’errore non era fondamentale, e passando il oggetto (riga) che l’ultimo elemento di cui avrebbe lavorato; allo stesso modo, .RemoveAt() con il più alto indice avrebbe funzionato:

    # Convert to [System.Collections.ObjectModel.Collection[psobject]]
    $dataResizable = { $data }.Invoke()
    
    # Remove last item by index.
    $dataResizable.RemoveAt($dataResizable.Count-1)
    
    # Alternative: Remove last item by object reference /value.
    # Returns [bool], which we suppress.
    $null = $dataResizable.Remove($dataResizable[-1])

    Tutto ciò che ha detto, utilizzando .Invoke() su un blocco di script solo per un ridimensionabile collezione è mal consigliato.

    Invece, scegliere uno dei metodi dimostrato in TessellatingHeckler risposta.

    Inoltre, PSv5+ offre un modo semplice per rimuovere l’ultimo elemento di una collezione passata attraverso un gasdotto, Select-Object -SkipLast 1:

    $data = Import-XLSX -Path "C:\Pathtofile\spreadsheet.xlsx" -Sheet "SheetName" -RowStart 3 |
      Select-Object -SkipLast 1

    OriginaleL’autore mklement0

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *