quinta-feira, 19 de julho de 2012

Batch rename usando PowerShell

[Atualização - 16/08/2013] Existe um bug no script. Veja aqui como resolvê-lo.

Problema: uma árvore de pastas com vários diretórios, subdiretórios. Uma zona nos nomes dos arquivos.

Objetivo: renomear todos os arquivos dentro de todas as pastas para uma sequência numérica mantendo a extensão original. Fundamental que, de acordo com o número de arquivos, os nomes sejam prefixados com zeros. Ex. numa pasta com 120 arquivos, o primeiro arquivo tem que se chamar 001.xyz, não 1.xyz.

Solução: PowerShell!

Ultimamente tenho dedicado algum tempo para entender o PowerShell melhor. Ainda falo como novato; adianto, contudo, que estou gostando.

Usei como base esse script:
http://justanothersysadmin.wordpress.com/2008/03/22/bulk-rename-files-with-sequential-index/

Fiz três adaptações:

1ª - Removi o prefixo. Sem utilidade para mim. Fácil colocar de volta caso necessário.
2ª - Adicionei um tolower() na variável da extensão para sanitizar as extensões maiúsculas para minúsculas.
3ª (IMPORTANTE!) - Fiz a variável $files ser populada apenas com a listagem de arquivos, sem os diretórios.

No meu arquivo de perfil (Microsoft.PowerShell_profile.ps1) tenho:
(ver aqui como criar um arquivo de perfil; aqui como permitir a execução de scripts)

Set-Location D:\Downloads
Clear-Host

function nomes($directory=$pwd)
{
$files = @( Get-ChildItem $directory | ? {!$_.PsIsContainer} )
$id = 1
$files | % { Rename-Item -Path $_.fullname -NewName ( ((($id++).tostring()).padleft(($files.count.tostring()).length) -replace ' ','0' ) + $_.extension.tolower() ) }
}

Ao chamar nomes sem argumento, a função usa o diretório corrente. Se especificá-lo, ele passa a ser usado.

Voltando ao problema, para não termos que ir manualmente em todas as pastas, o PowerShell faz isso para nós; ou seja, roda nomes dentro do diretório especificado e todos os seus subdiretórios, passando como argumento para a funçao o caminho completo de cada um:

Get-ChildItem -Path "C:\Caminho\Para\Pasta" -Recurse | ? {$_.PsIsContainer} | % { nomes $_.fullname }

(se remover -Path ele começa a partir do diretório corrente)

Legal! Toda aquela zona nos nomes dos arquivos agora está uniformizada em bonitinhas sequências numéricas. Os nomes das pastas não são alterados.

Observações:
@(...) diz para o PowerShell não converter a saída de array para scalars (é preciso para termos .count)
? é uma abreviação de Where-Object
% é uma abreviação de ForEach-Object

2 comentários:

  1. Marcos,

    Uma dúvida.
    Caso eu queira alterar apenas o começo do nome do arquivo.
    Exemplo: O arquivo C:\Downloads\MS-Sysinternals.exe ser alterado para C:\Downloads\0001-MS-Sysinternals.exe

    ResponderExcluir
    Respostas
    1. Em Rename-Item -NewName terá que fazer ele usar após o número a propriedade "BaseName". Algo assim:

      -NewName ( ((($id++).tostring()).padleft(($files.count.tostring()).length) -replace ' ','0' ) + "-" + $_.BaseName + $_.extension.tolower() )

      Não testado.

      Excluir