1
votes

Fusionner deux fichiers CSV tout en ajoutant de nouvelles entrées et en écrasant les entrées existantes

J'ai un configuration.csv qui contient un modèle de données comme celui-ci:

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item1 | value1 | ALL  |
| some/path  | item2 | value3 | ALL  |
| other/path | item1 | value2 | SOME |
| new/path   | item3 | value3 | SOME |

et customization.csv qui a un service spécifique configuration:

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item2 | value3 | ALL  |
| new/path   | item3 | value3 | SOME |

Mon objectif est de les fusionner et d'obtenir quelque chose comme ceci:

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item1 | value1 | ALL  |
| some/path  | item2 | UPDATE | ALL  |
| other/path | item1 | value2 | SOME |

Cela devrait ajoutez toutes les nouvelles entrées et mettez à jour toutes les entrées existantes. Aucune colonne ne peut être utilisée pour une identification unique - le chemin et le élément doivent être combinés, car ils sont garantis uniques.


0 commentaires

3 Réponses :


2
votes

Après de nombreuses recherches, j'ai pensé que le moyen le plus simple de manipuler les entrées sans recréer le cadre de gestion consistait à . Au cours du processus, j'ai dû tenir compte de deux cas extrêmes:

  1. virgules supplémentaires dans les valeurs
  2. valeurs vides

La solution finale que j'ai obtenue est la suivante:

$configuration = Import-Csv .\configuration.csv
$customization = Import-Csv .\customization.csv
$merged = New-Object System.Collections.ArrayList
$hashTable = @{}

#initializing the hashTable with the defaults
foreach ($entry in $configuration)
{
    $hashTable[$entry.path + ',' + $entry.item] = $entry.value + ',' + $entry.type
}

#updating the hashTable with customization that add or overwrite existing entries
foreach ($entry in $customization)
{
    $hashTable[$entry.path + ',' + $entry.item] = $entry.value + ',' + $entry.type
}

#the regex handles multiple commas and empty values.
#It returns an empty string before and after group so we start from 1 
foreach ($key in $hashTable.keys)
{
    $psobject = [PSCustomObject]@{
        path  = ($key -split '(.*),(.*)')[1]
        item  = ($key -split '(.*),(.*)')[2]
        value = ($hashTable[$key] -split '(.*),(.*)')[1]
        type  = ($hashTable[$key] -split '(.*),(.*)')[2]
    }
    [void] $merged.Add($psobject)
}
Write-Output $merged

Une fois importé, je transforme le configuration.csv en table de hachage avec des clés composé du chemin et de la valeur . Je fais ensuite de même avec customization.csv en utilisant le même hashTable qui écrase toutes les valeurs key existantes ou les ajoute comme nouvelles.

La troisième boucle convertit le hashTable à PSCustomObject similaire à ce que fait Import-Csv . J'ai divisé chacun des attributs key et value tout en tenant compte de plusieurs virgules et également de valeurs vides.
REMARQUE : l'expression régulière se divise à la dernière occurrence du séparateur (ici, c'est une virgule, mais vous pouvez sélectionner n'importe quoi, vraiment). Si vous souhaitez fractionner le premier, vous pouvez utiliser (. *?), (. *) . Dans mon cas, seule la colonne value pourrait contenir une instance du séparateur.

Si le CSV avait une colonne unique, alors une solution similaire à cette réponse aurait pu être utilisée.

Une autre alternative consiste à définir les clés comme la somme de toutes les colonnes, et cela filtrera tous les doublons dans le CSV, mais le fractionnement peut devenir délicat, en fonction des valeurs dans les colonnes.


3 commentaires

Bonne utilisation des tables de hachage qui sont en effet rapides car elles utilisent un algorithme de recherche binaire mais je Je crains que votre solution (où vous traitez les éléments liés par des chaînes) ne puisse pas tenir pour une solution réutilisable. Comme dans votre cas, il se cassera probablement si l'un de vos fichiers contient un guillemet simple (comme Frank's File.txt ) ou une virgule que vous utilisez comme séparateur ...


Je vois votre inquiétude. Dans mon cas, j'ai testé plusieurs virgules, mais uniquement dans la variable de gauche ( chemin ou valeur ). Les deux autres ne devraient pas avoir de virgules. Quant au '- je n'ai pas pu produire un cas où cela briserait le script. Il est traité correctement comme une chaîne.


Mon commentaire sur les guillemets est incorrect (car la chaîne de clés ne sera pas développée) mais le souci de la virgule reste, pour cela, vous pourriez envisager d'utiliser un caractère non imprimable pour séparer les champs à la place, par exemple: $ entry.path + [char] 31 + $ entry.item (il est très peu probable que cela soit utilisé dans n'importe quel nom de propriété).



2
votes

Je suggère d'utiliser Compare-Object et comme les valeurs de customization.csv doivent persister, utilisez les valeurs de ces fichiers comme -ReferenceObject

> Q:\Test\2019\03\01\SO_54948111.ps1

path       item  value  type
----       ----  -----  ----
some/path  item2 value3 ALL
some/path  item1 value1 ALL
other/path item1 value2 SOME
new/path   item3 value3 SOME

Exemple de sortie

## Q:\Test\2019\03\01\SO_54948111.ps1

$conf = Import-Csv '.\configuration.csv'
$cust = Import-Csv '.\customization.csv'

$NewData = Compare-Object -ref $cust -diff $conf -Property path,item -PassThru -IncludeEqual|
    Select-Object -Property * -ExcludeProperty SideIndicator

$NewData
$NewData |Export-Csv '.\NewData.csv' -NoTypeInformation


1 commentaires

Excellente solution. J'ai utilisé Comprae-Object dans le passé mais évidemment pas assez pour remarquer toutes les astuces utiles.



1
votes

Votre idée " utilisant le même hashTable qui écrase toutes les valeurs de clé existantes ou les ajoute comme nouvelles. " ne fonctionnera que si le chemin, élément est unique de chaque côté car vous écraserez également les doublons ... Considérez cette applet de commande Join-Object .

$ configuration = ConvertFrom-SourceTable '

$configuration | Merge $customization -on path, item

path       item  value  type
----       ----  -----  ----
some/path  item1 value1 ALL
some/path  item2 value3 ALL
other/path item1 value2 SOME
other/path item1 value3 ALL
new/path   item3 value3 SOME
new/path   item3 value4 ALL

$ customization = ConvertFrom-SourceTable '

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item2 | value3 | ALL  |
| new/path   | item3 | value3 | SOME |
| new/path   | item3 | value4 | ALL  |
'

En utilisant le Merge-Object , alias Merge , commande proxy (voir aide):

| path       | item  | value  | type |
|------------|-------|--------|------|
| some/path  | item1 | value1 | ALL  |
| some/path  | item2 | UPDATE | ALL  |
| other/path | item1 | value2 | SOME |
| other/path | item1 | value3 | ALL  |
'


3 commentaires

J'ai vu l'applet de commande lors de mes recherches, mais je voulais une solution autonome et ne reposant pas sur des dépendances externes.


Votre idée " utilisant la même table de hachage qui écrase toutes les valeurs de clé existantes ou les ajoute comme nouvelles. " ne fonctionnera que si le chemin, élément est unique de chaque côté. Voir les résultats dans l'exemple mis à jour de ma réponse et: stackoverflow.com/a/55543321/1701026


C'était ma demande initiale dans la question - mettre à jour les valeurs de tous les chemins existants. Comme il s'agit d'un fichier de configuration, j'ai besoin que chaque entrée soit unique et ne se répète pas. Votre solution est excellente mais pour des exigences différentes. Merci d'avoir pointé les autres questions.