我正在尝试编写一个自动化,以便我可以给出如下所述的输入并获得指示的输出,我正在使用Powershell来执行此操作。
INPUT : Song name in the format given below
Artist Name - Song Name.mp3
OUTPUT : Rename the song file in the format given below
Song Name - Artist Name.Mp3
我知道有OTS工具可以做到这一点,但我正在尝试使用PowerShell作为更大解决方案的一部分来做到这一点。
我有单行 PowerShell cmdlet 来获取项目,然后重命名它们。 所以我试了这个:
Get-ChildItem *.mp3 | Rename-Item -NewName {$_.Name.Substring($_.Name.IndexOf("-")+2, $_.Name.IndexOf(".")) +" - " + $_.Name.Substring(0, ($_.Name.IndexOf("-"))) + ".mp3"}
这就是问题所在。子字符串部分中的第二个 IndexOf 获取空值,因为它在同一(子字符串)操作中使用两次两次。
$_.Name.Substring($_.Name.IndexOf("-")+2, $_.Name.IndexOf("."))
这似乎是PowerShell的事情。以以下几行为例:
Get-ChildItem *.mp3 | Select-Object {$_.Name.Substring($_.Name.IndexOf("-")+2)}
Get-ChildItem *.mp3 | Select-Object {$_.Name.Substring($_.Name.IndexOf(".mp3"))}
当它们分开时,Ps 会为每次运行提供输出。但是,如果我同时运行这两个 cmdlet,则只有第一行提供输出,第二行返回 NULL。
如何获取以下逻辑的输出(我在这里做一个选择只是为了故障排除,因为我不想在故障排除时实际重命名)
Get-ChildItem *.mp3 | Select-Object {$_.Name.Substring($_.Name.IndexOf("-")+2, $_.Name.IndexOf(".mp3"))}
我可以使用变量和连接字符串,但如果可能的话,尝试将进程保持在一行中,而无需编写自定义代码/模块。
问题的根源在于你的Substring
表达。如果你把它从管道中解脱出来一会儿,你会看到有一个错误正在被吞噬......
PS> $s = "my artist - my song.mp3"
PS> $s.Substring($s.IndexOf("-") + 2, $s.IndexOf("."))
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string.
Parameter name: length"
At line:1 char:1
+ $s.Substring($s.IndexOf("-") + 2, $s.IndexOf("."))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ArgumentOutOfRangeException
问题是子字符串采用startIndex
和length
参数,但您正在尝试给它一个startIndex
和endIndex
。结果是,由于管道内引发异常,新名称为空字符串。
您可以使用正则表达式来解决此问题 - 例如:
PS> $s = "my artist - my song.mp3"
PS> $matches = [regex]::Match($s, "(?<artist>.*) - (?<song>.*).mp3")
PS> $newname = $matches.Groups["song"].Value + " - " + $matches.Groups["artist"].Value + ".mp3"
PS> $newname
my song - my artist.mp3
如果你把它放回你的管道中,你会得到:
Get-ChildItem *.mp3 | Rename-Item -NewName {
$matches = [regex]::Match($_.Name, "(?<artist>.*) - (?<song>.*).mp3")
$matches.Groups["song"].Value + " - " + $matches.Groups["artist"].Value + ".mp3"
}
但是在你承诺重命名整个音乐库之前,先和Select-Object
一起玩一下:-)
让我补充一下 mclayton 的有用答案,它很好地解释了您的尝试问题,并提供了一个优雅的基于正则表达式的解决方案。
至于这个问题的标题和这句话:
子字符串部分中的第二个 IndexOf 获取空值,因为它在同一(子字符串)操作中使用两次两次。
在脚本块中使用$_
的次数没有限制,如以下示例所示:
PS> 'one' | ForEach-Object { $_, $_.Substring(1), $_.SubString(2) -join '-' }
one-ne-e
从技术上讲,修改并因此丢弃$_
的值是可能的,但应该避免这样做 - 所有自动变量的修改也应该避免。
诸如.Substring()
之类的字符串方法从根本上返回输入字符串的(修改后的)副本- 它们永远不会就地修改它。
要提供基于-replace
运算符的简洁替代解决方案,请执行以下操作:
Get-ChildItem *.mp3 | Rename-Item -NewName {
($_.BaseName -replace '^(.+?) - (.+)', '$2 - $1') + $_.Extension
} -WhatIf
注意:上述命令中的-WhatIf
通用参数可预览操作。一旦您确定该操作将执行您想要的操作,请删除-WhatIf
。
在以您尝试的方式重命名这些文件时,我也没有结果。
我能够用更多的代码来解决这个问题。(如果你真的愿意,你可以把它塞成一行):
Expanded code for ease of reading:
#Set location of your mp3's
Set-Location "\FileServerTestShare"
#Create your array pf MP3s
$mp3s = Get-ChildItem *.mp3
Clear-Host
#loop through the tracks and rename them
foreach($mp3 in $mp3s)
{
#Display original name
Write-Host "`nOldName: " $mp3.Name -ForegroundColor Yellow
#Split the old name into individual pieces
$split = $mp3.Name.Split("-.")
#Remove excess spaces from string
$split = $split -replace (' ','')
#Create the new name
$unSplit = "$($split[1]) - $($split[0]).$($split[2])"
#Display the new name
Write-Host "NewName: "$unSplit -ForegroundColor Green
#Actually Rename the file
Rename-Item -Path $mp3.Name -NewName $unSplit
}
输出:
OldName: Farbrikam - MajorRelease1.mp3
NewName: MajorRelease1 - Farbrikam.mp3
OldName: Farbrikam - MajorRelease2.mp3
NewName: MajorRelease2 - Farbrikam.mp3
卡住 1 个衬垫:
$mp3s = Get-ChildItem *.mp3;foreach($mp3 in $mp3s) {Write-Host "`nOldName: " $mp3.Name -ForegroundColor Yellow;$split = $mp3.Name.Split("-.");$split = $split -replace (' ','');$unSplit = "$($split[1]) - $($split[0]).$($split[2])";Write-Host "NewName: "$unSplit -ForegroundColor Green;Rename-Item -Path $mp3.Name -NewName $unSplit}
您显然可以从此代码中删除 Write-Host,因为我使用它们来检查名称结构以确保它是我想要的样子。
此代码假定所有文件都将以以下格式命名: "艺术家姓名 - 歌曲名称.mp3">
例子:
Korn - Blind.mp3
Rob Zombie - Dragula.mp3
Luinz - I got 5 on it.mp3
希望这有帮助
.substring() 的第二个参数是length,而不是另一个索引。 但我可以看到混乱:
'012345'.substring(0,2)
01
但这不会在更高的指数中得到证实。 错误消息引用长度参数。
'01234'.substring(2,3)
234
'012345'.substring(2,5)
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string.
Parameter name: length"
您可以看到这样的函数定义,没有括号,当给出两个参数时,将长度显示为第二个参数:
'012345'.substring
OverloadDefinitions
string Substring(int startIndex)
string Substring(int startIndex, int length)
因此,您可以通过从第二个索引中减去第一个索引来获得长度。 我还在最后放了IndexOf(" -")
以摆脱一个空间。
Get-ChildItem *.mp3 |
Rename-Item -NewName {$_.Name.Substring($_.Name.IndexOf("-")+2, $_.Name.IndexOf(".") - ($_.Name.IndexOf("-")+2)) +
" - " +
$_.Name.Substring(0, ($_.Name.IndexOf(" -"))) + ".mp3"} -whatif
What if: Performing the operation "Rename File" on target "Item: /Users/js/foo/artist - song.mp3 Destination: /Users/js/foo/song - artist.mp3".
What if: Performing the operation "Rename File" on target "Item: /Users/js/foo/my artist - my song.mp3 Destination: /Users/js/foo/my song - my artist.mp3".
这是另一种方法,在' - '
上拆分:
Get-ChildItem *.mp3 |
rename-item -newname { $artist,$song = $_.basename -split ' - ';
"$song - $artist.mp3" } -whatif
任一解决方案都可以在一条线上。 取下 -whatif,如果它看起来不错。