【问题标题】:How to add a column to an existing CSV row in PowerShell?如何在 PowerShell 中向现有 CSV 行添加列?
【发布时间】:2018-04-08 21:51:19
【问题描述】:

我正在尝试在我的脚本中编写一个简单的使用记录器,该记录器将存储有关用户打开脚本、完成使用脚本和用户名的时间的信息。

我收集前两个数据的记录器的第一部分工作正常,并将两个带有值的必要列添加到 CSV 文件中。然而,当我运行记录器的第二部分时,它不会向我现有的 CSV 文件添加新列。

#Code I will add at the very beginning of my script
$FileNameDate = Get-Date -Format "MMM_yyyy"
$FilePath = "C:\Users\Username\Desktop\Script\Logs\${FileNameDate}_MonthlyLog.csv"
$TimeStamp = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
$UserName = [string]($env:UserName)
$LogArray = @()
$LogArrayDetails = @{
    Username = $UserName
    StartDate = $TimeStamp
}
$LogArray += New-Object PSObject -Property $LogArrayDetails | Export-Csv $FilePath -Notypeinformation -Append
#Code I will add at the very end of my script
$logArrayFinishDetails = @{FinishDate = $TimeStamp}
$LogCsv = Import-Csv $FilePath | Select Username, StartDate, @{$LogArrayFinishDetails} | Export-Csv $FilePath -NoTypeInformation -Append

脚本关闭后的 CSV 文件应如下所示:

Username    StartDate               FinishDate
anyplane    08/Apr/2018 23:47:55    08/Apr/2018 23:48:55

但它看起来像这样:

StartDate               Username                
08/Apr/2018 23:47:55    anyplane

另一个奇怪的事情是它把 StartDate 放在第一位,而我在 $LogArrayDetails 中明确指出 Username 放在第一位。

【问题讨论】:

  • 快速指针:Export-Csv 不产生任何输出,因此将其输出分配给$LogArray$LogCsv 毫无意义。

标签: powershell csv


【解决方案1】:

假设您只想记录最近一次运行 [如果您想记录多次运行,请参阅底部] (PSv3+) :

# Log start of execution.
[pscustomobject] @{ Username = $env:USERNAME; StartDate = $TimeStamp } |
  Export-Csv -Notypeinformation $FilePath

# Perform script actions...

# Log end of execution.
(Import-Csv $FilePath) | 
  Select-Object *, @{ n='FinishDate'; e={ (Get-Date).toString("dd/MMM/yyyy HH:mm:ss") } } |
    Export-Csv -Notypeinformation $FilePath

  • boxdog's helpful answer 中所述,将-AppendExport-Csv 一起使用不会添加额外的列。

  • 但是,由于您似乎是在尝试重写整个文件,因此根本没有必要使用
    -Append

      1234563但是,请将您的 Import-Csv $FilePath 呼叫附在 (...) 中。
      在这种情况下,对于 1 行文件来说,这并不是绝对必要的,但对于这种重写来说,这是一个好习惯;请注意,这种方法通常有点脆弱,因为在重写文件时可能会出现问题,从而导致潜在的数据丢失。
  • @{ n='FinishDate'; e={ (Get-Date).toString("dd/MMM/yyyy HH:mm:ss") } 是附加到预先存在的列 (*) 的 calculated property/column 示例


另一个奇怪的地方是它把 StartDate 放在第一位,而我在 $LogArrayDetails 中明确指出 Username 放在第一位。

您已使用哈希表 (@{ ... }) 来声明输出 CSV 的列,但不能保证枚举哈希表条目的顺序。

在 PSv3+ 中,您可以使用 ordered 哈希表 ([ordered] @{ ... }) 来实现可预测的枚举,如果将哈希表转换为 自定义对象通过转换为[pscustomobject],如上所示。


如果您确实想追加到现有文件,您可以使用以下内容,但请注意:

  • 这种方法不能很好地扩展,因为每次都会将整个日志文件读入内存(并转换为对象),尽管将条目限制为一个月的值应该没问题。

  • 如上所述,这种方法很脆弱,因为在重写文件时可能会出错;考虑简单地在每次执行时写入 2 行,这样您就可以逐行追加到文件中。

  • 没有并发管理,因此假设一次只运行一个脚本实例。

$FilePath = './t.csv'
$TimeStamp = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
$env:USERNAME = $env:USER

# Log start of execution. Note the empty 'FinishDate' property
# to ensure all rows ultimately have the same column structure.
[pscustomobject] @{ Username = $env:USERNAME; StartDate = $TimeStamp; FinishDate = '' } |
  Export-Csv -Notypeinformation -Append $FilePath

# Perform script actions...

# Log end of execution:
# Read the entire existing file...
$logRows = Import-Csv $FilePath
# ... update the last row's .FinishDate property
$logRows[-1].FinishDate = (Get-Date).toString("dd/MMM/yyyy HH:mm:ss")
# ... and rewrite the entire file, keeping only the last 30 entries
$logRows[-30..-1] | Export-Csv -Notypeinformation $FilePath

【讨论】:

    【解决方案2】:

    因为您的 CSV 已经有一个结构(即已定义的标题),PowerShell 在追加时会遵守这一点,并且不会添加其他列。在Export-Csvhelp的这段摘录中(某种程度上)解释了这一点:

    当您向 Export-CSV 提交多个对象时,Export-CSV 会组织 该文件基于您提交的第一个对象的属性。 如果其余对象不具有指定属性之一, 该对象的属性值为空,由两个表示 连续的逗号。 如果剩余的对象有额外的 属性,这些属性值不包含在文件中

    您可以在原始文件中包含 FinishDate 属性(即使它是空的),但最好的选择可能是在最后将您的输出导出到不同的 CSV,可能在导入后删除原始文件然后重新创建它与附加数据。事实上,只需删除 -Append 就可能会得到您想要的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-01
      • 2021-06-18
      • 2021-07-10
      • 1970-01-01
      • 2021-09-30
      • 2019-03-22
      • 1970-01-01
      • 2021-09-02
      相关资源
      最近更新 更多