代码之家  ›  专栏  ›  技术社区  ›  mklement0

大小写基于通配符模式规范化文件名

  •  1
  • mklement0  · 技术社区  · 6 年前

    如何根据匹配通配符模式的文本组件规范化文件名?

    请考虑以下文件名:

    ABC_1232.txt
    abC_4321.Txt
    qwerty_1232.cSv
    QwErTY_4321.CsV
    

    QWERTY_*.csv
    abc_*.TXT
    

    注意模式的文字成分(例如。, QUERTY_ , .csv ) 大小写不同 从上面列表中的匹配文件(例如。, QwErTY , .CsV ).

    图案 使用 完全正确 在文件名中;因此,生成的名称应为:

    abc_1232.TXT
    abc_4321.TXT
    QWERTY_1232.csv
    QWERTY_4321.csv
    

    帽子尖到 Vladimir Semashkin 为了启发这个问题。

    1 回复  |  直到 6 年前
        1
  •  2
  •   mklement0    6 年前

    术语注释:自 图案 可用于指通配符表达式和正则表达式,即 glob 用作通配符表达式的明确速记。


    基于字符串拆分的简单但有限的解决方案

    具体来说,下面的解决方案是 仅限于包含一个 * 作为唯一通配符元字符 .

    # Sample input objects that emulate file-info objects
    # as output by Get-ChildItem
    $files =
        @{ Name = 'ABC_1232.txt' },
        @{ Name = 'abC_4321.TxT' },
        @{ Name = 'qwerty_1232.cSv' },
        @{ Name = 'QwErTY_4321.CsV' },
        @{ Name = 'Unrelated.CsV' }
    
    # The wildcard patterns to match against.
    $globs = 'QWERTY_*.csv', 'abc_*.TXT'
    
    # Loop over all files.
    # IRL, use Get-ChildItem in lieu of $files.
    $files | ForEach-Object {    
      # Loop over all wildcard patterns
      foreach ($glob in $globs) {    
        if ($_.Name -like $glob) { # matching filename    
          # Split the glob into the prefix (the part before '*') and
          # the extension (suffix), (the part after '*').
          $prefix, $extension = $glob -split '\*'
    
          # Extract the specific middle part of the filename; the part that 
          # matched '*'
          $middle = $_.Name.Substring($prefix.Length, $_.Name.Length - $prefix.Length - $extension.Length)
    
          # This is where your Rename-Item call would go.
          #   $_ | Rename-Item -WhatIf -NewName ($prefix + $middle + $extension)
          # Note that if the filename already happens to be case-exact, 
          # Rename-Item is a quiet no-op.
          # For this demo, we simply output the new name.
          $prefix + $middle + $extension    
        }
      }
    }
    

    具有正则表达式的广义但更复杂的解

    这个解决方案要复杂得多,但是应该可以使用所有通配符表达式(只要 ` -不需要支持转义)。

    # Sample input objects that emulate file-info objects
    # as output by Get-ChildItem
    $files =
        @{ Name = 'ABC_1232.txt' },
        @{ Name = 'abC_4321.TxT' },
        @{ Name = 'qwerty_1232.cSv' },
        @{ Name = 'QwErTY_4321.CsV' },
        @{ Name = 'Unrelated.CsV' }
    
    # The globs (wildcard patterns) to match against.
    $globs = 'QWERTY_*.csv', 'abc_*.TXT'
    
    # Translate the globs into regexes, with the non-literal parts enclosed in
    # capture groups; note the addition of anchors ^ and $, given that globs
    # match the entire input string.
    # E.g., 'QWERTY_*.csv' -> '^QWERTY_(.*)\.csv$'
    $regexes = foreach($glob in $globs) {
      '^' +
        ([regex]::Escape($glob) -replace '\\\*', '(.*)' -replace  # *
                                         '\\\?', '(.)' -replace   # ?
                                         '\\(\[.+?\])', '($1)') + # [...]
      '$'
    }
    
    # Construct string templates from the globs that can be used with the -f
    # operator to fill in the variable parts from each filename match.
    # Each variable part is replaced with a {<n>} placeholder, starting with 0.
    # E.g., 'QWERTY_*.csv' -> 'QWERTY_{0}.csv'
    $templates = foreach($glob in $globs) {
      $iRef = [ref] 0
      [regex]::Replace(
        ($glob -replace '[{}]', '$&$&'), # escape literal '{' and '}' as '{{' and '}}' first
        '\*|\?|\[.+?\]', # wildcard metachars. / constructs
        { param($match) '{' + ($iRef.Value++) + '}' } # replace with {<n>} placeholders
      )
    }
    
    # Loop over all files.
    # IRL, use Get-ChildItem in lieu of $files.
    $files | ForEach-Object {
    
      # Loop over all wildcard patterns
      $i = -1
      foreach ($regex in $regexes) {
        ++$i
        # See if the filename matches
        if (($matchInfo = [regex]::Match($_.Name, $regex, 'IgnoreCase')).Success) {
          # Instantiate the template string with the capture-group values.
          # E.g., 'QWERTY_{0}.csv' -f '4321' 
          $newName = $templates[$i] -f ($matchInfo.Groups.Value | Select-Object -Skip 1)
    
          # This is where your Rename-Item call would go.
          #   $_ | Rename-Item -WhatIf -NewName $newName
          # Note that if the filename already happens to be case-exact, 
          # Rename-Item is a quiet no-op.
          # For this demo, we simply output the new name.
          $newName    
        }
      }
    }