如何在 Powershell 管道中多次使用 $_(子字符串方法)



我正在尝试编写一个自动化,以便我可以给出如下所述的输入并获得指示的输出,我正在使用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

问题是子字符串采用startIndexlength参数,但您正在尝试给它一个startIndexendIndex。结果是,由于管道内引发异常,新名称为空字符串。

您可以使用正则表达式来解决此问题 - 例如:

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,如果它看起来不错。

最新更新