Precisa renomear muitos nomes de arquivos muito longos com variáveis

2

Minha empresa recebe uma lista de arquivos todos os dias que precisamos processar e os nomes dos arquivos são quase impossíveis de serem tratados pelo nosso sistema. Existe uma maneira de renomear esses arquivos? Eu sou novo em qualquer tipo de script, então não tenho certeza por onde começar. Eu estou usando um sistema Windows. Eu tentei usar o Bulk Rename Utility, mas eu não sou capaz de descobrir como remover o AB_C_D_, e às vezes comete erros por alguma razão desconhecida que eu não descobri. Existe uma maneira de renomear esses arquivos usando o PowerShell?

É assim que os nomes dos arquivos se parecem agora:

Sample1_Sample2_1_05-11-2015_0_Sample3-AB_C_D_045_4_Sample4_123456.pdf

Isso é o que eu gostaria de fazer:

  • Remover Sample1 (que será sempre o mesmo)
  • Deixe Sample2 para que o nome do arquivo comece com Sample2 (sempre será o mesmo)
  • Remover _1
  • Deixe a data (que é uma data futura e será alterada)
  • Remover 0_Sample3 (que é sempre o mesmo)
    -Deixe o número da página (045, que será diferente em cada arquivo) e coloque-o após a data.
    -Remover _4_Sample4__
    -Deixe 123456 (este é um número de identificação e será diferente em cada arquivo).

O principal problema é que eu quero remover o AB_C_D_, e essas letras vão mudar. Pode haver mais ou menos (A_C_D_ por exemplo) e eu não sei como remover essa parte.


Assim, o nome do arquivo finalizado será Sample2_05-11-2015_045_123456.pdf

Se alguém puder me ajudar com isso ou me apontar na direção certa de como fazer isso, seria extremamente apreciado!

Obrigado antecipadamente, HH-GeekyGal

    
por Heidi 12.05.2015 / 02:03

2 respostas

0

Como Karan, expressões regulares são a maneira de fazer isso. Estou no linux, então não tenho certeza se o powershell tem buildins adequados, mas se não, baixe o sed para windows do sourceforge. É allaround awesomesauce.

Meu sed-fu é horribad, mas isso irá reformatar a string original para uma nova:

sed -r 's/Sample1_(Sample2_)[0-9]*_(..-..-....)_.*-[A-Z_]*(_[0-9][0-9]*_)._Sample4_(.)//'

Tenho certeza de que existem maneiras mais simples de realizar o mesmo.

Se você puder ler o bash, abaixo está um exemplo de como renomear:

for i in $(ls);do mv $i $(echo $i|sed -r 's/Sample1_(Sample2_)[0-9]*_(..-..-....)_.*-[A-Z_]*(_[0-9][0-9]*_)._Sample4_(.*)//');done

Sem dúvida, será simples o suficiente para ser roteirizado de forma semelhante em powershell, mas isso é deixado como exercício para o leitor: P

EDIT: erro de digitação

EDIT2: Olhou para o que eu escrevi e pode ser difícil de entender, então vou tentar mostrar o que eu estava tentando fazer:

No geral, a regex lê a linha e inclui as partes que queremos manter entre parênteses. Eles são chamados de padrões. Depois que a linha for lida, descarte tudo, exceto os padrões escolhidos.

sed -r   //-r switch is here only to allow the use of parens without escaping them. It's confusing enough without backslashes.
's/      //s is the command, stands for subtitute. syntax s/[search pattern]/[replace pattern]/. string matching SP is replaced with RP.
         //Here I use the command to match the whole line and save the parts I want.

Sample1_(Sample2_)  //set "Sample2_" as first pattern
[0-9]*_(..-..-....) //read onwards and skip zero or more numerals ([0-9]*) between two underscores. Read xx-xx-xxxx as second pattern where x is any character
_.*-[A-Z_]*(_[0-9][0-9]*_) //after underscore, skip any number of characters (.*) until run across dash. after that, skip any number of capital letters and underscores until you run into underscore followed by more than one numeral and underscore (_[0-9][0-9]*_). Save that as pat 3
._Sample4_(.*) //grab everything after Sample4_ as pat 4
//'   //First slash ends the search pattern for the s command and begin the . After that, , ,  and  insert patterns we saved in search part discarding the rest. final slash ends the s command.

regex é fácil de escrever, mesmo que seja difícil de ler. O que também significa que é fácil cometer erros e dificultar a depuração, mas não é possível ter todos.

Aqui está a essência do script shell no rabisco básico / python / pseudocode-ish.

for OLDNAME in DIRECTORY
     let NEWNAME = output of sed command with OLDNAME piped as input.
     rename OLDNAME NEWNAME
next
    
por 12.05.2015 / 03:33
0

Este script Powershell renomeará os arquivos da maneira que você precisar. Salve-o como RenameFiles.ps1 e execute a partir do console do PowerShell.

O script aceita os seguintes argumentos:

  • Caminho : Pasta exigida existente no disco, onde seus arquivos estão armazenados. Você pode fornecer vários caminhos.
  • Recurse : opção opcional, controla a recursão. Se especificado, o script renomeará os arquivos em todas as subpastas.
  • WhatIf : opção opcional, se especificado, o script relatará apenas nomes de arquivos novos e antigos. Nenhuma renomeação será feita.

Exemplos (executados no console do PowerShell):

  • Renomeie todos os arquivos na pasta c:\path\to\files :

    .\RenameFiles.ps1 -Path 'c:\path\to\files'
    
  • Renomeie todos os arquivos pdf na pasta c:\path\to\files :

    .\RenameFiles.ps1 -Path 'c:\path\to\files\*.pdf'
    
  • Renomeie todos os arquivos pdf na pasta c:\path\to\files , recurse

    .\RenameFiles.ps1 -Path 'c:\path\to\files\*.pdf' -Recurse
    
  • Verifica arquivos em várias pastas, recurse, reportar somente (sem renomear):

    .\RenameFiles.ps1 -Path 'c:\path\A\*.pdf', 'c:\path\B\*.psd' -Recurse -WhatIf
    

RenameFiles.ps1 do próprio script:

# Arguments accepted by script
Param
(
    # One or multiple paths, as array of strings
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [string[]]$Path,

    # Recurse switch
    [switch]$Recurse,

    # Whatif switch
    [switch]$WhatIf
)

# This function transforms long file name (w\o extension) to short via regex
function Split-FileName
{
    [CmdletBinding()]
    Param
    (
        # Original file name
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]$FileName
    )

    Begin
    {
        # You can change this block to adapt new rules for file renaming,
        # without modifying other parts of script.

        # Regex to match, capture groups are used to build new file name
        $Regex = '(Sample2).*(\d{2}-\d{2}-\d{4}).*(?<=[a-z]_)(\d+)(?=_\d+).*(?<=_)(\d+)$'

        # Scriptblock that builds new file name. $Matches is hashtable, but we need array for the format (-f) operator.
        # So this code: @(0..$Matches.Count | ForEach-Object {$Matches[$_]})} transforms it to the array.

        # Basically, we creating a new array of integers from 0 to count of $Matches keys, e.g. @(0,1,2,3,4,5)
        # and passing it down the pipeline. Then, in the foreach loop we output values of $Matches keys which name
        # match the current pipeline object, e.g. $Matches['1'], $Matches['2'], etc.
        # $Matches['0'] holds whole matched string, other keys hold capture groups.

        # This would also work:
        # $NewFileName = {'{0}_{1}_{2}_{3}{4}' -f $Matches['1'], $Matches['2'], $Matches['3'], $Matches['4'], $Matches['5']

        $NewFileName = {'{1}_{2}_{3}_{4}{5}' -f @(0..$Matches.Count | ForEach-Object {$Matches[$_]})}

    }

    Process
    {
        # If original file name matches regex
        if($FileName -match $Regex)
        {
            # Call scriptblock to generate new file name
            . $NewFileName
        }
    }
}

# For each path, get all file objects
Get-ChildItem -Path $Path -Recurse:$Recurse |
    # That are not directory
    Where-Object {!$_.PsIsContainer} |
        # For each file
        ForEach-Object {
            # Try to create new file name
            $NewBaseName = $_.BaseName | Split-FileName

            if($NewBaseName)
            {
                # If file name matched regex and we've got a new file name...

                # Build full path for the file with new name
                $NewFullName = Join-Path -Path $_.DirectoryName -ChildPath ($NewBaseName + $_.Extension)

                if(Test-Path -Path $NewFullName -PathType Leaf)
                {
                    # If such file already exists, show error message
                    Write-Host "File already exist: $NewFullName"
                }
                else
                {
                    # If not, rename it or just show report, depending on WhatIf switch
                    Rename-Item -Path $_.FullName -NewName $NewFullName -WhatIf:$WhatIf -Force
                }
            }
    }

Regex usado neste script: link (observe que a regex do PowerShell por padrão não faz distinção entre maiúsculas e minúsculas). Cópia da explicação de regex aqui:

Regex :

(Sample2).*(\d{2}-\d{2}-\d{4}).*(?<=[a-z]_)(\d+)(?=_\d+).*(?<=_)(\d+)$

Sample2 string:

1st Capturing group (Sample2)

Sample2 matches the characters Sample2 literally (case insensitive)

Qualquer caractere (não capturado e não existe na variável $Matches ):

.* matches any character (except newline)
Quantifier: * Between zero and unlimited times, as many times as possible,
giving back as needed [greedy]

Data :

2nd Capturing group (\d{2}-\d{2}-\d{4})

\d{2} match a digit [0-9]
Quantifier: {2} Exactly 2 times
- matches the character - literally

\d{2} match a digit [0-9]
Quantifier: {2} Exactly 2 times
- matches the character - literally

\d{4} match a digit [0-9]
Quantifier: {4} Exactly 4 times

Qualquer caractere (não capturado e não existe na variável $Matches ):

.* matches any character (except newline)
Quantifier: * Between zero and unlimited times, as many times as possible,
giving back as needed [greedy]

Número de páginas :

(?<=[a-z]_) Positive Lookbehind - Assert that the regex below can be matched

[a-z] match a single character present in the list below
a-z a single character in the range between a and z (case insensitive)
_ matches the character _ literally

3rd Capturing group (\d+)

\d+ match a digit [0-9]
Quantifier: + Between one and unlimited times, as many times as possible,
giving back as needed [greedy]

(?=_\d+) Positive Lookahead - Assert that the regex below can be matched
_ matches the character _ literally

\d+ match a digit [0-9]
Quantifier: + Between one and unlimited times, as many times as possible,
giving back as needed [greedy]

Qualquer caractere (não capturado e não existe na variável $Matches ):

.* matches any character (except newline)
Quantifier: * Between zero and unlimited times, as many times as possible,
giving back as needed [greedy]

Número de ID :

(?<=_) Positive Lookbehind - Assert that the regex below can be matched
_ matches the character _ literally

4th Capturing group (\d+)

\d+ match a digit [0-9]
Quantifier: + Between one and unlimited times, as many times as possible,
giving back as needed [greedy]
    
por 13.05.2015 / 01:05