Управление объектами Import-Csv и экспорт в текстовый файл построчно

В основном я пытаюсь создать подпрограмму, которая читает каталог с именованной папкой, создает 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 имеет свойства, называемые «Каталог», «Полное имя» и «Имя», заголовки трех столбцов в моем файле CSV.

Если мой файл CSV содержит следующие строки:

"Directory","FullName","Name"
"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 будет представлять собой массив из двух элементов: «C:\Test\OriginalDocs\A» и «C:\Test\OriginalDocs\B\».

Свойство FullName будет представлять собой массив из двух элементов: «C:\Test\OriginalDocs\A\file1_A_A_12345678.txt» и «C:\Test\OriginalDocs\B\file2_B_B_43534554.txt».

Свойство Name будет массивом из двух элементов: «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

Я искал foreach и ForEach-Object, но пока не нашел ничего подходящего для моих нужд. Может, это нельзя сделать?


person welshsteve147    schedule 04.01.2019    source источник
comment
Это долго читать, но на первый взгляд вам нужно 1) добавить пробел между -NoteProperty и Value. 2) ПРОВЕРЯЙТЕ, ПРОВЕРЯЙТЕ, ДВОЙНО ПРОВЕРЯЙТЕ любой пользовательский ввод, прежде чем использовать его. 3) используйте командлет Join-Path при объединении имен папок и файлов вместо жесткого кодирования обратной косой черты. Тогда... зачем использовать XCOPY вместо того, чтобы просто перебрать все строки в CSV, создать путь для этого конкретного файла и выполнить для него Copy-Item?   -  person Theo    schedule 04.01.2019
comment
Вы действительно должны прочитать, что такое минимально воспроизводимый пример, иначе вы быстро потеряете свою аудиторию. Как спросить   -  person    schedule 04.01.2019
comment
Привет @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 }   -  person welshsteve147    schedule 04.01.2019
comment
Для выполнения текста используйте Invoke-Expression. Но я бы предпочел использовать чистое решение powershell без xcopy.   -  person T-Me    schedule 04.01.2019
comment
Да, вам нужно убедиться, что путь к файлу существует. Вы делаете это с помощью командлетов Test-Path и New-Item.   -  person Theo    schedule 04.01.2019


Ответы (4)


Для работы построчно используйте foreach:

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

Вместо XCopy используйте New-Item, чтобы создать новый текстовый файл со значением старого файла или создать папку для нового элемента:

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

or

New-Item -Path'C:\Test\OriginalDocs\WantedDocs\12345678\*' -ItemType directory
person T-Me    schedule 04.01.2019

Бессмысленно экспортировать данные в 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
}
person Ansgar Wiechers    schedule 04.01.2019

Если вы действительно хотите выводить файлы 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, в противном случае ОК

person Theo    schedule 04.01.2019

Спасибо за все ответы. Используя комбинацию того, что было рекомендовано, у меня теперь есть нужное мне решение.

Большое спасибо за всю помощь. Я добавил раздел if else в обработку файлов, потому что меня интересуют только файлы, соответствующие определенному соглашению об именах (xx_xx_xx_clicode_xxx.ext). Это для конкретного проекта, где мне будут предоставлены тысячи файлов, большинство из которых должны соответствовать соглашению об именах. Поэтому я проверяю количество элементов в массиве переменных $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"

Большое спасибо еще раз. Очень признателен.

person welshsteve147    schedule 04.01.2019