Powershell Copy-Item 为同一路径提供 2 个不同的结果
Powershell Copy-Item gives 2 different results for same path
问题
我正在使用 powershell 编写部署脚本。使用 Copy-Item
命令不会在运行之间提供相同的结果,即使使用相同的数据也是如此。 (好像不是幂等的)
请参阅下面的结果示例,了解我的意思。我意识到我可以在复制 "new" 版本之前删除 C:\products
中的文件,但我更好奇这是否是 Powershell 中的预期行为。 (我假设是)
我宁愿学习 'correct' 的使用方法 Copy-Item
而不是绕过我的知识不足。
我试过使用 target\*
路径而不是 target
但这会将我的 lib
文件夹分解到 showcase
的根目录,这是我没有预料到的即使有 -Recurse
标志。
C:\products
- showcase
- showcase.jar
- logback.jar
- foo.jar
- junit.jar
- etc, etc
我对文档 here(特别是示例 7、10 和 11)也有些困惑,因为 7 使用了 -Recurse
标志,而 11 没有。同时示例 10 是这样说的:
If the scripts folder contains files in subfolders, those subfolders will be copied with their file trees intact.
但也从未指定递归标志。
结果
部署前
C:\products
- backup
初始部署
C:\products
- showcase
- showcase.jar
- lib
- <dependencies for showcase.jar>
- backup
<empty>
# Expected, as there wasn't a prior deployment yet.
第二次部署
C:\products
- showcase
- showcase.jar
- lib
- <dependencies for showcase.jar>
- target
- showcase.jar
- lib
- <dependencies for inner showcase.jar>
- backup
- showcase
- showcase.jar
- lib
- <dependencies for "old" showcase.jar>
# Expected, the old version was backed up
部署脚本及其调用方式
./deploy.ps1 -hostname foobar (it's a remote server)
部署。ps1
param( [String]$hostname, [switch]$debug )
$folder_to_copy = "target"
$init_file = "./init.ps1"
function Backup-Binary ( [String]$source, [String]$name, [String]$dest ) {
$FullyQualifiedSourcePath = "$source$name"
if (Test-Path $FullyQualifiedSourcePath) {
Write-Host "Backing up $FullyQualifiedSourcePath to the destination $dest"
Copy-Item -Recurse -Force -Path $FullyQualifiedSourcePath -Destination $dest -ErrorAction Stop
} else {
Write-Output "Directory $FullyQualifiedSourcePath didn't exist. May be an initial deployment. Continuing with deployment."
}
}
function Verify-Env ([String]$env_var) {
$check = [Environment]::GetEnvironmentVariable($env_var)
if ($check -eq $null) {
Write-Error "$env_var environment variable doesn't exist. Exiting program."
exit 1
}
}
function Deploy-Local {
# Checks that the given string exists as an Environment variable
Verify-Env "DEPLOY_DRIVE"
Verify-Env "CI_PROJECT_NAME"
# C:\products or D:\products *most* of the time.
$root = "$env:DEPLOY_DRIVE\products"
$destination = "$root$env:CI_PROJECT_NAME"
$backup_loc = "$root\backup"
# Removes prior binaries
Backup-Binary -source $root -name $env:CI_PROJECT_NAME -dest $backup_loc
# Copy binary locally since we're deploying to local machine.
Copy-Item -Recurse -Force -Path $folder_to_copy -Destination $destination -ErrorAction Stop
# Run the init script the developer has written
Invoke-Expression "$init_file" -ErrorAction Stop
}
function Deploy-Remote {
$session = New-PSSession -ComputerName $hostname
$DEPLOY_DRIVE = Invoke-Command -Session $session -ScriptBlock {
[Environment]::GetEnvironmentVariable("DEPLOY_DRIVE")
} -ErrorAction Stop
$root = "$DEPLOY_DRIVE\products"
$destination = "$root$env:CI_PROJECT_NAME"
$backup_loc = "$root\backup"
Write-Host "$root = root $destination = destination $backup_loc = backup_location"
Invoke-Command -Session $session -ScriptBlock ${function:Backup-Binary} -ArgumentList $root,$env:CI_PROJECT_NAME,$backup_loc -ErrorAction Stop
Copy-Item -Recurse -Force -ToSession $session -Path $folder_to_copy -Destination $destination -ErrorAction Stop
Invoke-Command -Session $session -FilePath $init_file -ErrorAction Stop
Remove-PSSession $session -ErrorAction Stop
}
if ( $hostname.ToLower() -eq $env:COMPUTERNAME.ToLower() ) {
Deploy-Local
} else {
Deploy-Remote
}
@mklement0 从 provided link(我在 github 上开始的一个问题)
给出了这个答案
@huffstler:
Yes, but that is the same problem as #2934, just in the context of a larger command:
- When subdir. a is being copied, $b doesn't exist yet, so a's content is being copied directly into $b (behavior (a) above).
- By the time subdir. b is copied, $b already exists, and that's when the inconsistency kicks in: b's content is copied to $b/b (behavior (b) above).
Therefore, if you run the command again, you do get the desired behavior.
Given the current behavior, you can work around the problem as follows:
Get-ChildItem -Path $a |
ForEach-Object { New-Item -Force -Type Directory $b } {
Copy-Item -Recurse -Path $_.FullName -Destination $b
}
But the larger question is whether this inconsistency should be resolved toward behavior (a) or behavior (b).
You clearly expected (a), but others may have different expectations, based on xcopy.
Frankly, I was baffled to find the same inconsistency as with Copy-Item -Recurse in the Unix world (cp -R).
The only way to currently get predictable behavior is to:
Ensure that the target dir. already exists.
Then, depending on whether you want behavior (a) or (b):
To get behavior (a): Explicitly target the content of the source directory:
Copy-Item -Recurse -Force $a/* $b
Note the need for -Force, which is needed to ensure that hidden items are copied too.
With cp on Unix, you can more simply just $a/.
, but that doesn't work in PowerShell.
To get behavior (b): No further action needed.
问题
我正在使用 powershell 编写部署脚本。使用 Copy-Item
命令不会在运行之间提供相同的结果,即使使用相同的数据也是如此。 (好像不是幂等的)
请参阅下面的结果示例,了解我的意思。我意识到我可以在复制 "new" 版本之前删除 C:\products
中的文件,但我更好奇这是否是 Powershell 中的预期行为。 (我假设是)
我宁愿学习 'correct' 的使用方法 Copy-Item
而不是绕过我的知识不足。
我试过使用 target\*
路径而不是 target
但这会将我的 lib
文件夹分解到 showcase
的根目录,这是我没有预料到的即使有 -Recurse
标志。
C:\products
- showcase
- showcase.jar
- logback.jar
- foo.jar
- junit.jar
- etc, etc
我对文档 here(特别是示例 7、10 和 11)也有些困惑,因为 7 使用了 -Recurse
标志,而 11 没有。同时示例 10 是这样说的:
If the scripts folder contains files in subfolders, those subfolders will be copied with their file trees intact.
但也从未指定递归标志。
结果
部署前
C:\products
- backup
初始部署
C:\products
- showcase
- showcase.jar
- lib
- <dependencies for showcase.jar>
- backup
<empty>
# Expected, as there wasn't a prior deployment yet.
第二次部署
C:\products
- showcase
- showcase.jar
- lib
- <dependencies for showcase.jar>
- target
- showcase.jar
- lib
- <dependencies for inner showcase.jar>
- backup
- showcase
- showcase.jar
- lib
- <dependencies for "old" showcase.jar>
# Expected, the old version was backed up
部署脚本及其调用方式
./deploy.ps1 -hostname foobar (it's a remote server)
部署。ps1
param( [String]$hostname, [switch]$debug )
$folder_to_copy = "target"
$init_file = "./init.ps1"
function Backup-Binary ( [String]$source, [String]$name, [String]$dest ) {
$FullyQualifiedSourcePath = "$source$name"
if (Test-Path $FullyQualifiedSourcePath) {
Write-Host "Backing up $FullyQualifiedSourcePath to the destination $dest"
Copy-Item -Recurse -Force -Path $FullyQualifiedSourcePath -Destination $dest -ErrorAction Stop
} else {
Write-Output "Directory $FullyQualifiedSourcePath didn't exist. May be an initial deployment. Continuing with deployment."
}
}
function Verify-Env ([String]$env_var) {
$check = [Environment]::GetEnvironmentVariable($env_var)
if ($check -eq $null) {
Write-Error "$env_var environment variable doesn't exist. Exiting program."
exit 1
}
}
function Deploy-Local {
# Checks that the given string exists as an Environment variable
Verify-Env "DEPLOY_DRIVE"
Verify-Env "CI_PROJECT_NAME"
# C:\products or D:\products *most* of the time.
$root = "$env:DEPLOY_DRIVE\products"
$destination = "$root$env:CI_PROJECT_NAME"
$backup_loc = "$root\backup"
# Removes prior binaries
Backup-Binary -source $root -name $env:CI_PROJECT_NAME -dest $backup_loc
# Copy binary locally since we're deploying to local machine.
Copy-Item -Recurse -Force -Path $folder_to_copy -Destination $destination -ErrorAction Stop
# Run the init script the developer has written
Invoke-Expression "$init_file" -ErrorAction Stop
}
function Deploy-Remote {
$session = New-PSSession -ComputerName $hostname
$DEPLOY_DRIVE = Invoke-Command -Session $session -ScriptBlock {
[Environment]::GetEnvironmentVariable("DEPLOY_DRIVE")
} -ErrorAction Stop
$root = "$DEPLOY_DRIVE\products"
$destination = "$root$env:CI_PROJECT_NAME"
$backup_loc = "$root\backup"
Write-Host "$root = root $destination = destination $backup_loc = backup_location"
Invoke-Command -Session $session -ScriptBlock ${function:Backup-Binary} -ArgumentList $root,$env:CI_PROJECT_NAME,$backup_loc -ErrorAction Stop
Copy-Item -Recurse -Force -ToSession $session -Path $folder_to_copy -Destination $destination -ErrorAction Stop
Invoke-Command -Session $session -FilePath $init_file -ErrorAction Stop
Remove-PSSession $session -ErrorAction Stop
}
if ( $hostname.ToLower() -eq $env:COMPUTERNAME.ToLower() ) {
Deploy-Local
} else {
Deploy-Remote
}
@mklement0 从 provided link(我在 github 上开始的一个问题)
给出了这个答案@huffstler:
Yes, but that is the same problem as #2934, just in the context of a larger command:
- When subdir. a is being copied, $b doesn't exist yet, so a's content is being copied directly into $b (behavior (a) above).
- By the time subdir. b is copied, $b already exists, and that's when the inconsistency kicks in: b's content is copied to $b/b (behavior (b) above).
Therefore, if you run the command again, you do get the desired behavior.
Given the current behavior, you can work around the problem as follows:
Get-ChildItem -Path $a | ForEach-Object { New-Item -Force -Type Directory $b } { Copy-Item -Recurse -Path $_.FullName -Destination $b }
But the larger question is whether this inconsistency should be resolved toward behavior (a) or behavior (b).
You clearly expected (a), but others may have different expectations, based on xcopy.
Frankly, I was baffled to find the same inconsistency as with Copy-Item -Recurse in the Unix world (cp -R).
The only way to currently get predictable behavior is to:
Ensure that the target dir. already exists.
Then, depending on whether you want behavior (a) or (b):
To get behavior (a): Explicitly target the content of the source directory:
Copy-Item -Recurse -Force $a/* $b
Note the need for -Force, which is needed to ensure that hidden items are copied too.
With cp on Unix, you can more simply just
$a/.
, but that doesn't work in PowerShell.To get behavior (b): No further action needed.