循环遍历并附加一个 hastable 而不是添加到键

Looping through and appending a hastable instead of adding to keys

我正在遍历 CSV 文件并使用 ForEach-Object 循环获取信息以尝试更新 Woocommerce 上的 in_stock 状态,最终发生的是 woocommerce 只看到一个条目。我不是程序员,我仍在学习 PowerShell,我终其一生都无法理解 for 循环的逻辑,并且它的输出正确。我知道它会读取 CSV 中的条目,但我认为它只是覆盖了之前的条目。 我遇到的另一个问题是将每个对象的 in_stock 值分别正确设置为 true 和 false,如果一个为 true,则所有 false 条目也设置为 true。我似乎无法弄清楚如何分配 true |错误正确。

我一直在使用 MS 文档查找 PowerShell 以及如何附加哈希表,但我仍然没有找到可以为我指明正确方向的答案或示例。我已经走到异地购买 PowerShell 教程,但仍然没有找到正确执行此操作的方法。

$website = "https://www.mywebsite.com"
$params += @{ 
    type= @();
    name = @();
    SKU = @();
    catalog_visibility = @();
    regular_price = @();
    in_stock = @();
    categories = @();
}

$csv = Import-Csv C:\test\test-upload.csv
$csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog', 
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer 
reviews?', 'Regular price', Categories | ForEach-Object{ 
$params.type += $_.type
$params.SKU += $_.SKU
$params.name += $_.name 
$params.catalog_visibility += $_.'Visibility in catalog'
$params.categories += $_.Categories
$params.regular_price += $_.'Regular price'
$params.in_stock += $_.'In stock?'
if ($params.in_stock = 0) {$params.in_stock -replace 0, $false} 
 elseif($params.in_stock = 1) {$params.in_stock -replace 1, $true}   
}

foreach($key in $params.keys){

    Write-Output $params[$key]
} 

我正在寻找这样的东西

    {
      "name": "part 1",
      "type": "simple",
      "SKU": "0001",
      "regular_price": "21.99",
      "in_stock: false",
      "categories: category 1",
      "catalog_visibility": "hidden",
    },
    {
      "name": "part 2",
      "type": "simple",
      "SKU": "0002",
      "regular_price": "11.99",
      "in_stock: true",
      "categories: category 2",
      "catalog_visibility": "hidden",
    }

我实际得到的是

    {
      "name": "part 1 part 2",
      "type": "simple simple ",
      "SKU": "0001 0002",
      "regular_price": "21.99 11.99",
      "in_stock: true true",
      "categories: category 1 category 1",
      "catalog_visibility": "hidden hidden",
    }

如果有人能指出正确的方向并给我一些最佳实践提示,我将不胜感激

所以您正在做的是很多 += 来尝试创建数组,但是您在错误的级别上进行操作。您想要做的是为 CSV 中的每个项目创建一个哈希表(或者很可能是一个 PSCustomObject),并将它们捕获为一个对象数组(无论是哈希表对象还是 PSCustomObject 对象)。因此,让我们尝试稍微调整一下结构来做到这一点。我放弃了模板,我们不在乎,无论如何我们正在为每个对象定义它。我将为 ForEach-Object 循环中的每个项目输出一个哈希表,并在 $params 中捕获它。这应该会给你想要的结果。

$website = "https://www.mywebsite.com"

$csv = Import-Csv C:\test\test-upload.csv
$params = $csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog', 'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer reviews?', 'Regular price', Categories | ForEach-Object{ 
    @{
        type = $_.type
        SKU = $_.SKU
        name = $_.name 
        catalog_visibility = $_.'Visibility in catalog'
        categories = $_.Categories
        regular_price = $_.'Regular price'
        in_stock = [boolean][int]($_.'In stock?')
    }
}

由于您是编程新手,所以我们来谈谈数组和哈希表。

数组就像列表(有时也称为列表),具体来说,是按位置排序的列表。

哈希表是一种字典,因此您有一个键对应一个值。

在 PowerShell 中,您用于创建数组的语法是 @()(它是空的,它可以包含项目),您用于创建哈希表的语法是 @{}(也空,可以包含值)。

您没有显示 $params 的初始定义,但根据其余代码,我假设它是这样的:

$params = @()

那么,你有这个:

$params += @{ 
    type= @();
    name = @();
    SKU = @();
    catalog_visibility = @();
    regular_price = @();
    in_stock = @();
    categories = @();
}

所以这意味着您使用数组 $params 并向其中添加了一个新项目。新项目是您在此处定义的哈希表文字。您添加的所有名称,如 typenameSKU 等都是键。

根据您想要的输出,看起来您想要一个哈希表数组,所以我认为那部分是正确的。

但是请注意,您分配给它们的都是空数组。这很好奇,因为您显示的所需输出具有每个哈希表,这些键是奇异值,所以我认为这是一个问题,实际上它使问题所在的区域变得模糊。

让我们跳到循环体,您将在其中使用此模式:

$params.type += $_.type
$params.SKU += $_.SKU
$params.name += $_.name 
$params.catalog_visibility += $_.'Visibility in catalog'
$params.categories += $_.Categories
$params.regular_price += $_.'Regular price'
$params.in_stock += $_.'In stock?'

请记住 $params 是一个数组,因此您应该从位置 0 开始在其中包含项目,例如 $params[0]$params[1] 等。要更改第二个的 SKU数组中的哈希表,您将使用 $params[1].SKU$params[1]['SKU'].

但你所做的只是$params.SKU。在许多语言中,实际上在 v3 之前的 PowerShell 中,这会引发错误。数组本身没有名为 SKU 的 属性。在 PowerShell v3 中,虽然点 . 运算符得到增强以允许它内省数组和 return 每个项目的 属性 具有给定的名称,即:

$a = @('apple','orange','pear')
$a.PadLeft(10,'~')

和我们做的一样:

$a = @('apple','orange','pear')
$a | ForEach-Object { $_.PadLeft(10,'~') }

它非常有用,但在这里可能会让您感到困惑。

所以回到你的对象,$params 是一个数组,到目前为止,其中只有一个哈希表。在您的循环中,您没有向 $params.

添加任何内容

相反,你要求 $params.SKU,在这种情况下,它将是数组中每个哈希表的 SKU,但只有一个哈希表,所以你只能得到一个 SKU .

然后你添加到 SKU 本身:

$params.SKU += $_.SKU

这是最初将 SKU 设置为空数组的部分隐藏了您的问题。如果 SKU 是一个字符串,这将失败,因为字符串不支持 +=,但由于它是一个数组,您将采用这个新值,并将其添加到 SKU 的数组 作为您正在处理的单个哈希表的值存在。


从这里去哪里

  1. 在这种情况下不要使用数组作为您的值
  2. 在循环的每次迭代中创建一个 new 哈希表,然后 将新哈希表添加$params 数组

一起来看看:

$params = @()

$csv = Import-Csv C:\test\test-upload.csv
$csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog', 
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer 
reviews?', 'Regular price', Categories | ForEach-Object { 
$params += @{  # new hashtable here
    type = $_.type
    SKU = $_.SKU
    name = $_.name 
    catalog_visibility = $_.'Visibility in catalog'
    categories = $_.Categories
    regular_price = $_.'Regular price'
  }
}

这是您遇到的主要问题,我省略了库存部分,因为我将单独解释该逻辑。


$params.in_stock = $_.'In stock?'
if ($params.in_stock = 0) {$params.in_stock -replace 0, $false} 
 elseif($params.in_stock = 1) {$params.in_stock -replace 1, $true}   
}

您的 CSV 文件中似乎有一个 In stock? 列,对于 false/true。

可以是 01

首先我要说明的是 PowerShell 中的 = 始终是赋值。相等性测试是 -eq,所以:

$params.in_stock = $_.'In stock?'
if ($params.in_stock -eq 0) {$params.in_stock -replace 0, $false} 
 elseif($params.in_stock -eq 1) {$params.in_stock -replace 1, $true}   
}

接下来说一下true/false值;它们简称为 Boolean 或 bool,您通常应该使用这种数据类型来表示它们。任何时候你做一个比较,例如 $a -eq 5 你是 returning a bool。

强烈支持将其他类型转换为 bool,例如,如果您想将数字评估为 bool,0 为 false,而所有其他值均为 true。对于字符串,$null 值或空字符串为假,所有其他值为真。请注意,如果您有一个字符串 "0"true,因为该字符串有一个值。

这也意味着数字 0 与字符串 '0' 不同,但 PowerShell 确实尝试在类型之间进行转换,通常是尝试将右侧的类型转换为左侧的类型边进行比较,因此 PowerShell 会告诉您 0 -eq '0' 是正确的(与 '0' -eq 0 相同)。

对于您的情况,从 CSV 读取,这些值将以字符串形式结束,但由于上述原因,您的相等性测试无论如何都会起作用(值得了解详细信息)。

不过,您使用 -replace 的问题在于它是一个字符串操作,所以即使它有效,您最终也会得到布尔值的字符串表示形式,而不是实际的布尔值,即使您说直接使用 $true$false(这又是因为类型转换;-replace 需要一个字符串,PowerShell 将您的 bool 转换为字符串以满足它)。

所以,经过那冗长的解释,有道理的是:

$params.in_stock = $_.'In stock?'
if ($params.in_stock -eq 0) {
    $params.in_stock = $false
} elseif($params.in_stock -eq 1) {
    $params.in_stock -eq $true
}

事实上,elseif 不是必需的,因为您只能有 2 个值:

$params.in_stock = $_.'In stock?'
if ($params.in_stock -eq 0) {
    $params.in_stock = $false
} else {
    $params.in_stock -eq $true
}

更进一步,我们可以使用转换来完全不需要条件。还记得我说过的关于将字符串转换为数字,以及将数字转换为布尔值的内容吗。

0 -as [bool]    # gives false
"0" -as [bool]  # gives true (whoops)
"0" -as [int]   # gives the number 0
"0" -as [int] -as [bool]  # false!

现在,我们可以这样做:

$params.in_stock = $_.'In stock?' -as [int] -as [bool]

酷!让我们把它放回其他代码中:

$params = @()

$csv = Import-Csv C:\test\test-upload.csv
$csv | Select-Object -Property Type, SKU, Name, 'Visibility in catalog', 
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer 
reviews?', 'Regular price', Categories | ForEach-Object { 
$params += @{  # new hashtable here
    type = $_.type
    SKU = $_.SKU
    name = $_.name 
    catalog_visibility = $_.'Visibility in catalog'
    categories = $_.Categories
    regular_price = $_.'Regular price'
    in_stock = $_.'In stock?' -as [int] -as [bool]
  }
}

深入研究!

管道:您正在执行一些调用,例如 Import-Csv 调用并将其输出分配给一个变量,然后将该变量通过管道传输到另一个命令。没关系,没错,但是您也可以像这样将第一个命令的输出直接通过管道传输到第二个命令:

$params = @()

Import-Csv C:\test\test-upload.csv |
    Select-Object -Property Type, SKU, Name, 'Visibility in catalog', 
'Tax status', 'In stock?', Stock, 'Backorders allowed?', 'Allow customer 
reviews?', 'Regular price', Categories | 
    ForEach-Object { 
        $params += @{  # new hashtable here
            type = $_.type
            SKU = $_.SKU
            name = $_.name 
            catalog_visibility = $_.'Visibility in catalog'
            categories = $_.Categories
            regular_price = $_.'Regular price'
            in_stock = $_.'In stock?' -as [int] -as [bool]
         }
     }

我对格式进行了一些更新,以表明您可以在竖线后使用换行符 |,这样看起来会更干净一些。

关于Select-Object:它的目的是获取具有特定属性集的对象,并返回给你一个具有更有限(或有时是全新)属性的新对象(它还有其他用途更改对象的数量或以其他方式过滤数组,这些方式目前与此处无关)。

但我提出这个问题,因为您选择的所有属性(列)都是按名称命名的,因此必须存在于输入对象中。由于您稍后会直接引用每一个而不是显示整个内容,因此没有理由使用 Select-Object 来过滤属性,以便可以删除整个调用:

$params = @()

Import-Csv C:\test\test-upload.csv |
    ForEach-Object { 
        $params += @{  # new hashtable here
            type = $_.type
            SKU = $_.SKU
            name = $_.name 
            catalog_visibility = $_.'Visibility in catalog'
            categories = $_.Categories
            regular_price = $_.'Regular price'
            in_stock = $_.'In stock?' -as [int] -as [bool]
        }
    }

不错!看起来很苗条。

关于数组和 +=。老实说,这在大多数情况下是可以的,但是您应该知道,每次执行此操作时,实际上都会创建一个新数组,并且所有原始项目加上新项目都是被复制到其中。这不会扩展,但在大多数用例中也很好。

您还应该知道的是,管道的输出(如任何命令,或您的主脚本代码,或 ForEach-Object 的主体都被发送到管道中的下一个命令(或返回如果没有其他内容,则在左侧。这可以是任意数量的项目,您可以使用赋值来获取所有这些值,例如:

$a = Get-ChildItem $env:HOME  # get all of items in the directory

$a如果有一个以上的项目,将是一个数组,并且在处理过程中它不会不断地创建和销毁数组。

那么这与您有什么关系呢?这意味着您不必使 $params 成为一个空数组并附加到它,只需 return 每次循环迭代中的新哈希表,然后将管道的输出分配给 $params!

$params = Import-Csv C:\test\test-upload.csv |
    ForEach-Object { 
        @{  # new hashtable here
            type = $_.type
            SKU = $_.SKU
            name = $_.name 
            catalog_visibility = $_.'Visibility in catalog'
            categories = $_.Categories
            regular_price = $_.'Regular price'
            in_stock = $_.'In stock?' -as [int] -as [bool]
        } # output is implicit
    }

现在我们已将您的脚本简化为单个管道(您可以将其设为单行,但我更喜欢多行格式)。