【问题标题】:Manipulating Import-Csv objects and exporting to text file line by line操作 Import-Csv 对象并逐行导出到文本文件
【发布时间】:2019-05-30 22:44:45
【问题描述】:

我基本上是在尝试构建一个读取命名文件夹目录的例程,构建一个 CSV 文件,然后读入该 CSV 文件,操作一些属性以将数据拆分为新列,然后将其导出到另一个 CSV。

这是我通过以下代码实现的:

$Folder = Read-Host 'Please enter a folder path'
$File = Read-Host 'Please enter a filename'
$OutputFile = $Folder + '\' + $File + '.csv'
$SplitFile = $Folder + '\' + $File + '_Split.csv'
$CopyDir = $Folder + '\WantedDocs\'

Get-ChildItem $Folder -Recurse -Include *.* |
    select Directory, FullName, Name |
    Export-Csv -Delimiter ',' -NoTypeInformation $OutputFile

$a = Import-Csv $OutputFile 
$values = $a.Name.Split("_") 
$a | Add-Member 'CliCode' -NotePropertyValue $values[3]
$a | Add-Member 'CopyDir' -NotePropertyValue $CopyDir

$a | Select Directory, FullName, Name, CliCode, CopyDir |
    Export-Csv -Delimiter ',' -NoTypeInformation $SplitFile

如果我的术语不正确,请原谅,但我现在希望使用属性中的项目值构建一个包含 xcopy 命令的批处理文件。

xcopy 'C:\Test\OriginalDocs\A\file1_A_A_12345678.txt' 'C:\Test\WantedDocs\*' /Y

当您使用 Import-Csv 并将其分配给变量时,该变量包含各种属性,每个属性都包含从 CSV 文件中的每一行获取的值数组。

在我的示例中,变量 $a 具有名为“Directory”、“FullName”和“Name”的属性,它们是我的 CSV 文件中 3 列的标题。

如果我的 CSV 文件包含以下行:

"目录","全名","名称" "C:\Test\OriginalDocs\A","C:\Test\OriginalDocs\A\file1_A_A_12345678.txt","file1_A_A_12345678.txt" "C:\Test\OriginalDocs\B","C:\Test\OriginalDocs\B\file2_B_B_43534554.txt","file1_B_B_43534554.txt"

Directory 属性将是一个包含 2 项的数组:“C:\Test\OriginalDocs\A”和“C:\Test\OriginalDocs\B\”

FullName 属性将是一个包含 2 项的数组:“C:\Test\OriginalDocs\A\file1_A_A_12345678.txt”和“C:\Test\OriginalDocs\B\file2_B_B_43534554.txt”

Name 属性将是一个包含 2 项的数组:“file1_A_A_12345678.txt”和“file2_B_B_43534554.txt”

我想知道的是如何为每个属性选择数组中的所有 [0] 项并构建 xcopy 命令

例如如果我这样做:

$xc1 = "xcopy '"
$xc2 = $a.FullName
$xc3 = "' '"
$xc4 = $a.CopyDir
$xc5 = $a.CliCode
$xc6 = "\*' /Y"
$xcopy = $xc1 + $xc2 + $xc3 + $xc4 + $xc5+ $xc6

生成的$xcopy 变量包含所有数组值

例如对于上面的示例,xcopy 变量以值结束:

xcopy 'C:\Test\OriginalDocs\A\file1_A_A_12345678.txt C:\Test\OriginalDocs\B\file2_B_B_43534554.txt' 'C:\Test\OriginalDocs\WantedDocs\ C:\Test\OriginalDocs\WantedDocs\12345678 43534554\*' /Y

我想要实现的是使用每个选定属性的 [0] 数组值有效地做到这一点:

$xc1 = "xcopy '"
$xc2 = $a.FullName[0]
$xc3 = "' '"
$xc4 = $a.CopyDir[0]
$xc5 = $a.CliCode[0]
$xc6 = "\*' /Y"
$xcopy = $xc1 + $xc2 + $xc3 + $xc4 + $xc5+ $xc6

$xcopy 变量写入文本文件(我相信使用Add-Content

然后对 [1] 数组值执行相同操作:

$xc1 = "xcopy '"
$xc2 = $a.FullName[1]
$xc3 = "' '"
$xc4 = $a.CopyDir[1]
$xc5 = $a.CliCode[1]
$xc6 = "\*' /Y"
$xcopy = $xc1 + $xc2 + $xc3 + $xc4 + $xc5+ $xc6

以此类推,直到处理完数组中的所有项目。

因此,为数组中的每个项目(即所有 [0]、所有 [1] 等)生成一个文本/批处理文件。

使用上面的示例,我会得到一个如下所示的文本文件。

xcopy 'C:\Test\OriginalDocs\A\file1_A_A_12345678.txt' 'C:\Test\OriginalDocs\WantedDocs\12345678\*' /Y
xcopy 'C:\Test\OriginalDocs\B\file2_B_B_43534554.txt' 'C:\Test\OriginalDocs\WantedDocs\43534554\*' /Y

我一直在查看foreachForEach-Object,但到目前为止我还没有找到任何适合我需要的东西。可能做不到?

【问题讨论】:

  • 读起来很长,但乍一看,您需要 1) 在-NotePropertyValue 之间添加一个空格。 2)在使用之前检查,检查,双重检查任何用户输入。 3) 在组合文件夹和文件名时使用Join-Path cmdlet,而不是硬编码反斜杠。那么.. 为什么要使用 XCOPY 而不是简单地遍历 CSV 中的所有行,为该特定文件创建路径并在其上执行 Copy-Item
  • 你真的应该阅读minimal reproducible example 是什么,否则你会很快失去你的观众。 How-to ask
  • 嗨@Theo 感谢您的回复。我需要在将文件复制到的目标目录中创建新目录。据我了解,如果源项目是文件,则 Copy-Item 不会创建新文件夹。尝试此操作时出现错误: $b = Import-Csv $SplitFile Foreach ($Row in $b) { $OriginalFile = $($Row.FullName) $CopyFile = $CopyDir + $($Row.CliCode) Copy -Item $OriginalFile -Destination $CopyFile -Force -Recurse }
  • 执行“文本”使用Invoke-Expression。但我宁愿使用没有xcopy 的纯powershell 解决方案。
  • 是的,您需要确保文件的路径存在。您可以使用 Test-PathNew-Item cmdlet 来做到这一点。

标签: arrays powershell csv import-csv export-csv


【解决方案1】:

要逐行工作,请使用foreach:

foreach ($Line in $a){ DoSomethingLikeCopy $Line.FullName to "$CopyDir\$($Line.CliCode)"   }  

使用 New-Item 代替 XCopy 使用旧文件的值创建新文本文件或为新项目创建文件夹:

Get-Content -Path 'C:\Test\OriginalDocs\A\file1_A_A_12345678.txt' -raw | New-Item -Path 'C:\Test\OriginalDocs\WantedDocs\12345678\file1_A_A_12345678.txt' -Force

New-Item -Path'C:\Test\OriginalDocs\WantedDocs\12345678\*' -ItemType directory

【讨论】:

    【解决方案2】:

    将数据导出到您正在读取的 CSV 文件是没有意义的。只需使用管道。另外,xcopy 是一个外部命令,可以直接从 PowerShell 中运行,因此无需先让 PowerShell 创建批处理文件。

    这应该就是你所需要的:

    $Folder = Read-Host 'Please enter a folder path'
    Get-ChildItem $Folder -Recurse | ForEach-Object {
        $clicode = $_.BaseName.Split("_")[-1]
        & xcopy $_.FullName "${Folder}\WantedDocs\${clicode}\*" /y
    }
    

    【讨论】:

      【解决方案3】:

      如果您确实希望在每一步都输出 CSV 文件,您可以执行以下操作:

      # YOU NEED TO ADD CODE FOR CHECKING THE USER INPUT
      # What I'm doing here is very rudimentary..
      do {
          $Folder = Read-Host 'Please enter a folder path'
      } while (-not (Test-Path -Path $Folder -PathType Container))
      
      $File = Read-Host 'Please enter a filename (no extension)'
      
      
      # at the very least sanitize the given filename, and get only the Name without Extension
      $BaseName   = [System.IO.Path]::GetFileNameWithoutExtension($File)
      $OutputFile = Join-Path -Path $Folder -ChildPath ($BaseName + '.csv')
      $SplitFile  = Join-Path -Path $Folder -ChildPath ($BaseName + '_Split.csv')
      $CopyDir    = Join-Path -Path $Folder -ChildPath 'WantedDocs'
      
      # collect the files and get the properties Directory, FullName and Name
      $a = Get-ChildItem $Folder -Recurse -Include *.* -File | Select-Object Directory,FullName,Name
      
      # write the first CSV file:
      $a | Export-Csv -Path $OutputFile -Delimiter ',' -NoTypeInformation
      
      # redefine the collection to add extra properties CliCode, CopyDir and Result
      $a = $a | Select-Object *,CliCode,CopyDir,Result
      # loop through the collection
      $a | ForEach-Object {
          # the automatic variable $_ is a single object in the collection
          # get the CliCode from the Name property:
          # if the filename is "file1_A_A_12345678.txt", the CliCode will be "12345678"
          if ($_.Name -match '([^_.]+)\..*$') {
              $cliCode   = $matches[1]
              $targetDir = Join-Path -Path $CopyDir -ChildPath $cliCode
              $_.CliCode = $cliCode       # example: "12345678"
              $_.CopyDir = $targetDir     # example: "C:\Test\WantedDocs\12345678"
      
              # copy the file, but create the target folder first if this does not exist
              if (-not (Test-Path -Path $targetDir -PathType Container)) {
                  New-Item -Path $targetDir -ItemType Directory | Out-Null
              }
              Copy-Item -Path $_.FullName -Destination $targetDir
              $_.Result = "OK"
          } 
          else {
              # show the error and add "Failure" to the result property
              Write-Warning "Skipped file '$_.FullName'. Reason: CliCode not found"
              $_.Result = "Failure"
          }
      
      }
      
      # output the results of the copy as CSV file
      $a | Export-Csv -Path $SplitFile -Delimiter ',' -NoTypeInformation
      

      完成后,文件将复制到新位置,您将拥有两个 CSV 文件: 第一个“Something.csv”副本之前:

      "Directory","FullName","Name"
      "D:\Test\OriginalDocs\A","D:\Test\OriginalDocs\A\file1_A_A_12345678.txt","file1_A_A_12345678.txt"
      "D:\Test\OriginalDocs\B","D:\Test\OriginalDocs\B\file2_B_B_43534554.txt","file2_B_B_43534554.txt"
      

      和第二个“Something_Split.csv”副本之后:

      "Directory","FullName","Name","CliCode","CopyDir","Result"
      "D:\Test\OriginalDocs\A","D:\Test\OriginalDocs\A\file1_A_A_12345678.txt","file1_A_A_12345678.txt","12345678","D:\Test\OriginalDocs\WantedDocs\12345678","OK"
      "D:\Test\OriginalDocs\B","D:\Test\OriginalDocs\B\file2_B_B_43534554.txt","file2_B_B_43534554.txt","43534554","D:\Test\OriginalDocs\WantedDocs\43534554","OK"
      

      如果文件名中不包含 CliCode,“结果”列将显示 Failure,否则 OK

      【讨论】:

        【解决方案4】:

        感谢您的所有回复。结合所建议的内容,我现在有了我需要的解决方案。

        非常感谢您提供的所有帮助。我在文件处理中添加了 if else 部分,因为我只对遵循特定命名约定 (xx_xx_xx_clicode_xxx.ext) 的文件感兴趣。这是针对一个特定项目,我将在其中提供 1000 个文件,其中大部分文件应遵循命名约定。所以我正在检查 $values 变量数组中的元素数量,以确保它至少有 4 个值(即 [3] 作为值存在)。在不存在的地方,我将文件名写入日志文件。

        这是完整的解决方案:

        do {
            $Folder = Read-Host 'Please enter a folder path'
        } while (-not (Test-Path -Path $Folder -PathType Container))
        
        $File = Read-Host 'Please enter a filename (no extension)'
        
        $OutputFile = Join-Path -Path $Folder -ChildPath ($File + '.csv')
        $SplitFile = Join-Path -Path $Folder -ChildPath ($File + '_Split.csv')
        $CopyDir = Join-Path $Folder -ChildPath 'WantedDocs'
        
        $logfile = "log.txt"
        $log = Join-Path -Path $Folder -ChildPath $logfile
        
        Get-ChildItem $Folder -Recurse -Include *.* | select Directory,FullName,Name | Export-Csv -Delimiter ',' -NoTypeInformation $OutputFile
        
        $a = Import-Csv $OutputFile 
        
        $a | Add-Member 'CopyDir' -NotePropertyValue $CopyDir
        
        $a | Select Directory,FullName,Name,CopyDir | Export-Csv -Delimiter ',' -NoTypeInformation $SplitFile
        
        Foreach ($Row in $a)
        {
            $values = $Row.Name.split("_") 
            If ($values.Count -gt 3)
            {
                $tempfile = Join-Path -Path $CopyDir -ChildPath $values[3]
                $OriginalFile = $($Row.FullName)
                $CopyFile = $tempfile
                New-Item -ItemType directory -Path $tempfile -Force
                Copy-Item $OriginalFile -Destination $CopyFile -Force -Recurse
            }
            Else
            {
                Add-Content $log $Row.Name
            }
        }
        
        Write-Output "Finished"
        

        再次非常感谢。非常感谢。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-02-02
          • 1970-01-01
          • 1970-01-01
          • 2016-03-26
          • 1970-01-01
          • 2016-02-20
          • 2017-07-01
          相关资源
          最近更新 更多