質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.98%

2つのCSVファイルを比較して、合致する項目があれば別ファイルとして出力したい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 5,669

SUSU0703

score 13

こんにちは。Powershellの質問です。宜しくお願いします。

以下のような2つのCSVファイルがあり、もし[user.csv]のemployeeIDがmachine.csvのNameに含まれていれば、
(今回は11111と22222)

[user.csv]
employeeID    EmployeeName       City       Description
12345            田中                       京都
11111            中村                       福岡       広報
34567            須賀                       東京
22222            古市                       大阪       第2営業部

[machine.csv]
Name           operatingSystem             EmployeeName    City    Description
JP00000            Win7            
JP11111            Mac            
JP22222            Win8            
JP33333            Win10            

このように、別CSVとして出力し、machines.csvのName、operatingSystemコラムと
user.csvのEmployeeName、City、Descriptionコラムを合体させたいと思います。
[Merge.csv]
Name           operatingSystem             EmployeeName    City    Description
JP11111            Mac                          中村                    福岡    広報
JP22222            Win8                        古市                    大阪    第2営業部

自分で試してはみたものの、あまり詳しくない為全く出力できません。ぜひ、解決方法を教えて頂ければと思います。
どうぞよろしくお願いいたします。

$users = import-csv "./User.csv"
$machines = import-csv "./Machine.csv"

foreach ($line in $machines) {
if($machines.'Name' -match $users.'employeeID'){

Write-output $($machines.'Name' + ":" + $machines.'operatingSystem' + ":" + $users.'EmployeeName' + ":" + $users.'City' + ":" + $users.'Description') | Out-file "./Merge.csv" -append

}
}

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

内部結合がしたいということですね。
SQL は使ったことがありますか?
マイクロソフトの PowerShell チームのブログに使い回しできる関数がありますのでこれを使ってみてください。

Join-Object

追記

カレントディレクトリに user.csv と machine.csv があるときに result.csv に書き込みます。
result.csv が既にあるとエラーになります。

function AddItemProperties($item, $properties, $output) {
    if ($item -ne $null) {
        foreach ($property in $properties) {
            $propertyHash = $property -as [hashtable]
            if ($propertyHash -ne $null) {
                $hashName = $propertyHash["name"] -as [string]
                if ($hashName -eq $null) {
                    throw "there should be a string Name"  
                }

                $expression = $propertyHash["expression"] -as [scriptblock]
                if ($expression -eq $null) {
                    throw "there should be a ScriptBlock Expression"  
                }

                $_ = $item
                $expressionValue = & $expression

                $output | add-member -MemberType "NoteProperty" -Name $hashName -Value $expressionValue
            }
            else {
                # .psobject.Properties allows you to list the properties of any object, also known as "reflection"
                foreach ($itemProperty in $item.psobject.Properties) {
                    if ($itemProperty.Name -like $property) {
                        $output | add-member -MemberType "NoteProperty" -Name $itemProperty.Name -Value $itemProperty.Value
                    }
                }
            }
        }
    }
}


function WriteJoinObjectOutput($leftItem, $rightItem, $leftProperties, $rightProperties, $Type) {
    $output = new-object psobject

    if ($Type -eq "AllInRight") {
        # This mix of rightItem with LeftProperties and vice versa is due to
        # the switch of Left and Right arguments for AllInRight
        AddItemProperties $rightItem $leftProperties $output
        AddItemProperties $leftItem $rightProperties $output
    }
    else {
        AddItemProperties $leftItem $leftProperties $output
        AddItemProperties $rightItem $rightProperties $output
    }
    $output
}

<#
.Synopsis
   Joins two lists of objects
.DESCRIPTION
   Joins two lists of objects
.EXAMPLE
   Join-Object $a $b "Id" ("Name","Salary")
#>
function Join-Object {
    [CmdletBinding()]
    [OutputType([int])]
    Param
    (
        # List to join with $Right
        [Parameter(Mandatory = $true,
            Position = 0)]
        [object[]]
        $Left,

        # List to join with $Left
        [Parameter(Mandatory = $true,
            Position = 1)]
        [object[]]
        $Right,

        # Condition in which an item in the left matches an item in the right
        # typically something like: {$args[0].Id -eq $args[1].Id}
        [Parameter(Mandatory = $true,
            Position = 2)]
        [scriptblock]
        $Where,

        # Properties from $Left we want in the output.
        # Each property can:
        # – Be a plain property name like "Name"
        # – Contain wildcards like "*"
        # – Be a hashtable like @{Name="Product Name";Expression={$_.Name}}. Name is the output property name
        #   and Expression is the property value. The same syntax is available in select-object and it is 
        #   important for join-object because joined lists could have a property with the same name
        [Parameter(Mandatory = $true,
            Position = 3)]
        [object[]]
        $LeftProperties,

        # Properties from $Right we want in the output.
        # Like LeftProperties, each can be a plain name, wildcard or hashtable. See the LeftProperties comments.
        [Parameter(Mandatory = $true,
            Position = 4)]
        [object[]]
        $RightProperties,

        # Type of join. 
        #   AllInLeft will have all elements from Left at least once in the output, and might appear more than once
        # if the where clause is true for more than one element in right, Left elements with matches in Right are 
        # preceded by elements with no matches. This is equivalent to an outer left join (or simply left join) 
        # SQL statement.
        #  AllInRight is similar to AllInLeft.
        #  OnlyIfInBoth will cause all elements from Left to be placed in the output, only if there is at least one
        # match in Right. This is equivalent to a SQL inner join (or simply join) statement.
        #  AllInBoth will have all entries in right and left in the output. Specifically, it will have all entries
        # in right with at least one match in left, followed by all entries in Right with no matches in left, 
        # followed by all entries in Left with no matches in Right.This is equivallent to a SQL full join.
        [Parameter(Mandatory = $false,
            Position = 5)]
        [ValidateSet("AllInLeft", "OnlyIfInBoth", "AllInBoth", "AllInRight")]
        [string]
        $Type = "OnlyIfInBoth"
    )

    Begin {
        # a list of the matches in right for each object in left
        $leftMatchesInRight = new-object System.Collections.ArrayList

        # the count for all matches  
        $rightMatchesCount = New-Object "object[]" $Right.Count

        for ($i = 0; $i -lt $Right.Count; $i++) {
            $rightMatchesCount[$i] = 0
        }
    }

    Process {
        if ($Type -eq "AllInRight") {
            # for AllInRight we just switch Left and Right
            $aux = $Left
            $Left = $Right
            $Right = $aux
        }

        # go over items in $Left and produce the list of matches
        foreach ($leftItem in $Left) {
            $leftItemMatchesInRight = new-object System.Collections.ArrayList
            $null = $leftMatchesInRight.Add($leftItemMatchesInRight)

            for ($i = 0; $i -lt $right.Count; $i++) {
                $rightItem = $right[$i]

                if ($Type -eq "AllInRight") {
                    # For AllInRight, we want $args[0] to refer to the left and $args[1] to refer to right,
                    # but since we switched left and right, we have to switch the where arguments
                    $whereLeft = $rightItem
                    $whereRight = $leftItem
                }
                else {
                    $whereLeft = $leftItem
                    $whereRight = $rightItem
                }

                if (Invoke-Command -ScriptBlock $where -ArgumentList $whereLeft, $whereRight) {
                    $null = $leftItemMatchesInRight.Add($rightItem)
                    $rightMatchesCount[$i]++
                }

            }
        }

        # go over the list of matches and produce output
        for ($i = 0; $i -lt $left.Count; $i++) {
            $leftItemMatchesInRight = $leftMatchesInRight[$i]
            $leftItem = $left[$i]

            if ($leftItemMatchesInRight.Count -eq 0) {
                if ($Type -ne "OnlyIfInBoth") {
                    WriteJoinObjectOutput $leftItem  $null  $LeftProperties  $RightProperties $Type
                }

                continue
            }

            foreach ($leftItemMatchInRight in $leftItemMatchesInRight) {
                WriteJoinObjectOutput $leftItem $leftItemMatchInRight  $LeftProperties  $RightProperties $Type
            }
        }
    }

    End {
        #produce final output for members of right with no matches for the AllInBoth option
        if ($Type -eq "AllInBoth") {
            for ($i = 0; $i -lt $right.Count; $i++) {
                $rightMatchCount = $rightMatchesCount[$i]
                if ($rightMatchCount -eq 0) {
                    $rightItem = $Right[$i]
                    WriteJoinObjectOutput $null $rightItem $LeftProperties $RightProperties $Type
                }
            }
        }
    }
}
$users = Import-Csv .\user.csv | ForEach-Object { $_.employeeID = "JP" + $_.employeeID; $_ }
$machines = Import-Csv .\machine.csv
Join-Object $users $machines -Where { $args[0].employeeID -eq $args[1].Name }`
    -LeftProperties EmployeeName, City, Description -RightProperties Name, operatingSystem |
    Select-Object Name, operatingSystem, EmployeeName, City, Description |
    Export-Csv .\result.csv -NoClobber -NoTypeInformation -Encoding utf8

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/10 22:23

    それなら
    -Where { $args[1].Name.EndsWith($args[0].employeeID) }
    のようにしてみたらどうですか?
    もちろん JP を足しているところは削除してください。

    キャンセル

  • 2018/03/10 22:24

    EndsWith は .NET の System.String のメソッドですから調べてください。

    キャンセル

  • 2018/03/10 23:43

    バッチリできました!! 本当にありがとうございます。助かりました。自力ではとても無理でした。。
    感謝感謝です!!!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.98%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる