2

Моя компания каждый день получает список файлов, которые нам нужно обработать, и имена файлов для нашей системы практически невозможны. Есть ли способ переименовать эти файлы? Я новичок в любом типе сценариев, поэтому я не уверен, с чего начать. Я использую систему Windows. Я пытался использовать Bulk Rename Utility, но я не могу понять, как удалить AB_C_D_, и он иногда делает ошибки по неизвестной причине, которую я не понял. Есть ли способ переименовать эти файлы с помощью PowerShell?

Вот как теперь выглядят имена файлов:

Sample1_Sample2_1_05-11-2015_0_Sample3-AB_C_D_045_4_Sample4_123456.pdf

Вот что я хотел бы сделать:

  • Удалить Sample1 (который всегда будет одинаковым)
  • Оставьте Sample2 чтобы имя файла начиналось с Sample2 (всегда будет одинаковым)
  • Удалить _1
  • Оставьте дату (которая является датой в будущем и изменится)
  • Удалить 0_Sample3 (который всегда одинаков)
    -Оставьте номер страницы (045, который будет отличаться в каждом файле) и поместите его после даты.
    -Удалить _4_Sample4_
    -Оставьте 123456 (это идентификационный номер, который будет отличаться в каждом файле).

Основная проблема заключается в том, что я хочу удалить AB_C_D_, и эти буквы будут меняться. Там может быть больше или меньше (например, A_C_D_), и я не знаю, как удалить эту часть.


Таким образом, готовое имя файла будет Sample2_05-11-2015_045_123456.pdf

Если бы кто-нибудь мог помочь мне с этим или указать мне правильное направление, как это сделать, это было бы чрезвычайно ценно!

Заранее спасибо, HH-GeekyGal

2 ответа2

0

Этот скрипт Powershell переименует файлы так, как вам нужно. Сохраните его как RenameFiles.ps1 и запустите из консоли PowerShell.

Скрипт принимает следующие аргументы:

  • Путь: Обязательно, существующая папка на диске, где хранятся ваши файлы.Вы можете указать несколько путей.
  • Recurse: опциональный переключатель, контролирует рекурсию.Если указано, скрипт будет переименовывать файлы во всех подпапках.
  • WhatIf: Необязательный переключатель, если он указан, скрипт будет сообщать только о новых и старых именах файлов.Переименование не будет сделано.

Примеры (запускаются из консоли PowerShell):

  • Переименуйте все файлы в папке c:\path\to\files:

    .\RenameFiles.ps1 -Path 'c:\path\to\files'
    
  • Переименуйте все pdf файлы в папке c:\path\to\files:

    .\RenameFiles.ps1 -Path 'c:\path\to\files\*.pdf'
    
  • Переименуйте все pdf файлы в папке c:\path\to\files , recurse

    .\RenameFiles.ps1 -Path 'c:\path\to\files\*.pdf' -Recurse
    
  • Сканирование файлов в нескольких папках, повтор, только отчет (без переименования):

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

Сам скрипт RenameFiles.ps1 :

# 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
                }
            }
    }

В этом сценарии используется регулярное выражение: https://regex101.com/r/hT2uN9/2 (обратите внимание, что регулярное выражение PowerShell по умолчанию не учитывает регистр). Копия объяснения регулярного выражения здесь:

Регулярное выражение:

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

Строка Sample2 :

1st Capturing group (Sample2)

Sample2 matches the characters Sample2 literally (case insensitive)

Любой символ (не захвачен и не существует в переменной $Matches ):

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

Дата:

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

Любой символ (не захвачен и не существует в переменной $Matches ):

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

Количество страниц:

(?<=[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]

Любой символ (не захвачен и не существует в переменной $Matches ):

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

Идентификационный номер:

(?<=_) 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]
0

Как и в случае с Karan, регулярные выражения являются способом сделать это. Я нахожусь на Linux, поэтому я не уверен, что у PowerShell есть подходящие сборки, но если нет, скачайте sed для windows с sourceforge. Это универсальный потрясающий соус.

Мой сед-фу ужасен, но он переформатирует исходную строку в новую:

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

Я уверен, что есть более простые способы сделать то же самое.

Если вы можете прочитать bash, ниже приведен пример, как его переименовать:

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

Без сомнения, это будет достаточно просто, чтобы написать сценарий подобным в PowerShell, но это оставлено в качестве упражнения для читателя:P

EDIT: опечатка

РЕДАКТИРОВАТЬ 2: Посмотрел, что я написал, и это может быть трудно понять, поэтому я попытаюсь показать, что я пытался сделать:

В целом, регулярное выражение читает строку и заключает в скобки те части, которые мы хотим оставить. Они называются узорами. После прочтения строки откажитесь от всего, кроме выбранных шаблонов.

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
/\1\2\3\4/'   //First slash ends the search pattern for the s command and begin the . After that, \1, \2, \3 and \4 insert patterns we saved in search part discarding the rest. final slash ends the s command.

регулярное выражение легко написать, даже если его трудно прочитать. Это также означает, что с ошибками легко и сложно отлаживать, но вы не можете иметь все.

Вот суть сценария оболочки в базовом /python /pseudocode-ish scribble.

for OLDNAME in DIRECTORY
     let NEWNAME = output of sed command with OLDNAME piped as input.
     rename OLDNAME NEWNAME
next

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .