Zexks Marquise
Zexks Marquise

Reputation: 1607

Single line piped loop with File Check and Rename

So I'm trying to rename a bunch of MSSQL backups that come out like:

DBName_backup_2017_12_20_564451321_567987465.bak

To Something like

DBName.bak

But also have a safety check to ensure that if DBName.bak is already taken it'll do DBName_1.bak. Where 1 will be an incremental variable until there is a valid unused filename.

I was able to do it with the following code:

Get-ChildItem *_*.bak | % { 
    # Set the new name, replace everything after the first underscore '_' with
    # '.bak'
    $newName = &{$_.Name -replace $_.Name.Substring($_.Name.IndexOf("_")), '.bak'}

    # Check if new name exists
    for ($cnt = 1; (Test-Path $newName) -eq $true; $cnt++) { 
        # If it already exists add '_' and a number check again until unused
        # filename is found
        $newName = &{$newName -replace '.bak', "_$cnt.bak"} 
    }

    # Rename file to new filename. Uncomment WhatIf for testing.
    Rename-Item -Path $_ -NewName $newName #-WhatIf
}

What I'm trying to do now, is to one line it with piping, but am having no luck. Particularly getting the loop to run with the Test-Path check. Does anyone know how I could one like this?

Upvotes: 0

Views: 47

Answers (1)

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200233

Simple:

$cnt = 0; Get-ChildItem *_*.bak | Rename-Item -NewName {
    ($_.BaseName -replace '_.*', '_') + $script:cnt++ + $_.Extension
} -WhatIf

If you just want to (re)number files with duplicate database names you can't truly one-line the thing. You need something like this:

Get-ChildItem *_*.bak | ForEach-Object {
    $basename = $_.BaseName -replace '_.*'
    $newname  = $basename + $_.Extension
    $script:cnt = 1
    while (Test-Path $newname) {
        $newname = $basename + '_' + $script:cnt++ + $_.Extension
    }
    Rename-Item -NewName $newname -WhatIf
}

However, in PowerShell you can separate statements with both newlines and semicolons, so you can still merge all of the above into one line. Like this:

Get-ChildItem *_*.bak | ForEach-Object {$basename = $_.BaseName -replace '_.*'; $newname  = $basename + $_.Extension; $script:cnt = 1; while (Test-Path $newname) {$newname = $basename + '_' + $script:cnt++ + $_.Extension}; Rename-Item -NewName $newname -WhatIf}

I don't normally recommend doing this, though, because it makes code unnecessarily hard to read and debug.

Either way, remove the the chicken switch after you verified renaming would work as desired.

Upvotes: 1

Related Questions