PowerShell 将字符串插入到文件名中,就在扩展名之前

PowerShell insert a string into a filename, just before the extension

我经常需要对文件做的一件事是 create/reference 文件的备份版本。例如我可能想要一个文件的 date/time 标记版本,以便我可以在整个脚本中引用原始报告和备份版本:

$now = $(Get-Date -format "yyyy-MM-dd__HH-mm")
$ReportName = "MyReport-en.csv"
$Backups = "D:\Backups\Reports"

我发现使用 -join+ 总是在 date/time 戳之前插入一个 space:

$ReportBackup = "$Backups$($ReportName -split ".csv")" + "_$now.csv"
$ReportBackup = "$Backups$($ReportName -split ".csv")" -join "_$now.csv"

我找到了一种方法来做到这一点,但它看起来对三元组 $.csv

的重复来说效率很低
$ReportBackup = "$Backups$($($ReportName -split ".csv")[0])$_now.csv"

这导致:

$ReportBackup => D:\Backups\Reports\MyReport-en_2022-04-15__07-55.csv

您能想出 simpler/cleaner 方法来实现在扩展名之前插入一段文本的通用目标,而无需三重 $ 或重复扩展名吗? (“使用 $name = "MyReport-en"”不是很有用,因为我经常读取一个文件对象并获得带有扩展名的完整名称。

如果您通过使用 Get-ItemGet-ChildItem 获得报告文件作为 FileInfo object,您会发现该对象具有方便的属性可用于创建包含日期的新文件名:

# assume $ReportName is a FileInfo object
$Backups = "D:\Backups\Reports"
# I'm creating a new filename using the '-f' Format operator
$NewName = '{0}_{1:yyyy-MM-dd__HH-mm}{2}' -f $ReportName.BaseName, (Get-Date), $ReportName.Extension
$ReportBackup = Join-Path -Path $Backups -ChildPath $NewName

如果 $ReportName 只是一个只包含文件名的字符串,您可以这样做:

$ReportName = "MyReport-en.csv"
$Backups    = "D:\Backups\Reports"
$baseName   = [System.IO.Path]::GetFileNameWithoutExtension($ReportName)
$extension  = [System.IO.Path]::GetExtension($ReportName)
$NewName    = '{0}_{1:yyyy-MM-dd__HH-mm}{2}' -f $baseName, (Get-Date), $extension
$ReportBackup = Join-Path -Path $Backups -ChildPath $NewName

P.S。简单地在文件名上使用 .Replace() 总是有风险的,因为这不允许您锚定要替换的子字符串,而这是必需的,因为该子字符串很可能也是名称本身的一部分。
此外,字符串 .Replace() 方法有效 case-sensitive.

这意味着

'My.csvReport-en.csv'.Replace(".csv", "_$(Get-Date -Format 'yyyy-MM-dd__HH-mm').csv")

会失败 (returns My_2022-04-15__13-36.csvReport-en_2022-04-15__13-36.csv)

'MyReport-en.CSV'.Replace(".csv", "$(Get-Date -Format 'yyyy-MM-dd__HH-mm').csv")

不会替换任何东西,因为它找不到大写 .CSV ..

如果您真的想通过将扩展名替换为日期+扩展名来做到这一点,请使用更复​​杂的 case-insensitive 正则表达式 -replace,例如:

$ReportName -replace '^(.+)(\.[^.]+)$', "`_$(Get-Date -Format 'yyyy-MM-dd__HH-mm')`" 

正则表达式详细信息:

^              Assert position at the beginning of the string
(              Match the regular expression below and capture its match into backreference number 1
   .           Match any single character that is not a line break character
      +        Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)             
(              Match the regular expression below and capture its match into backreference number 2
   \.          Match the character “.” literally
   [^.]        Match any character that is NOT a “.”
      +        Between one and unlimited times, as many times as possible, giving back as needed (greedy)
)             
$              Assert position at the end of the string (or before the line break at the end of the string, if any)
$now = Get-Date -Format "yyyy-MM-dd__HH-mm"
$reportName = "MyReport-en.csv"
$backups = "D:\Backups\Reports"

$reportBackup = Join-Path $backups $reportName.Replace(".csv","_$now.csv")
$reportBackup

P.S。 如果您知道它的工作原理,使用 .Replace() 没有任何风险。 此方法区分大小写并替换所有匹配项。在这种特殊情况下,我们提前知道确切的名称,因此我们可以安全地使用此方法。

名字“My.csvReport-en.csv”是一句废话,但是如果问题明确提到通用解决方案“any-name.ext”:

$reportName = "My.aSd_Report-en.aSD"
$backups = "D:\Backups\Reports"

$reportBackup = Join-Path $backups ($reportName -replace "(?=\.[^.]+$)", (Get-Date -Format "_yyyy-MM-dd__HH-mm"))
$reportBackup