使用 AppleScript 将文件名中的无效字符替换为破折号

Replacing invalid characters in the filename with dashes using AppleScript

我的目标是使用 AppleScript 或 Javascript 在 Automator 中创建服务,它用破折号 (-) 替换所选文件名 ()[\/:"*?<>|]+_ 和空格的所有无效字符,并使文件名小写。

借助正则表达式和桥接到 AppleScriptObjC 的基础框架,这非常容易。

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

use framework "Foundation"

set fileName to "New(Foo)*aBcd<B|r.ext"

set nsFileName to current application's NSString's stringWithString:fileName
set nsLowerCaseFileName to nsFileName's lowercaseString()
set trimmedFileName to (nsLowerCaseFileName's stringByReplacingOccurrencesOfString:"[()[\/:\"*?<>|]+_]" withString:"-" options:(current application's NSRegularExpressionSearch) range:{location:0, |length|:nsLowerCaseFileName's |length|()}) as text
display dialog trimmedFileName

可以通过在您的 Automator 服务中使用 Bash Shell 脚本来替换 file/folder 名称中不允许的字符。

以下步骤描述了如何实现这一点:

配置 Automator

  1. 启动 Automator
  2. 键入 ⌘N,或从菜单栏中选择 File > New
  3. Select Service 然后点击 Choose
  4. 在 canvas 区域的顶部配置其设置如下:

  5. Select Library 左上方panel/column:

    • 在搜索字段中键入:获取 Select Finder 项目 并将 Get Select Finder items 操作拖到 canvas面积.

    • 在搜索字段中键入:运行 Shell 并将 Run Shell Script 操作拖到 canvas 面积.

  6. 配置Run Shell Script动作的顶部如下:

  7. 将以下 Bash 脚本添加到 Run shell Script 操作的主要区域:

    #!/usr/bin/env bash
    
    # The following characters are considered impermissible in a basename:
    #
    #  - Left Square Bracket:   [
    #  - Right Square Bracket:  ]
    #  - Left Parenthesis:      (
    #  - Reverse Solidus:       \
    #  - Colon:                 :
    #  - Quotation Mark         "
    #  - Single Quotation Mark  '
    #  - Asterisk               *
    #  - Question Mark          ?
    #  - Less-than Sign         <
    #  - Greater-than Sign      >
    #  - Vertical Line          |
    #  - Plus Sign              +
    #  - Space Character
    #  - UnderScore             _
    #
    #  1. Sed is utilized for character replacement therefore characters listed
    #     in the bracket expression [...] must be escaped as necessary.
    #  2. Any forward slashes `/` in the basename are substituted by default with
    #     a Colon `:` at the shell level - so it's unnecessary to search for them.
    #
    declare -r IMPERMISSIBLE_CHARS="[][()\:\"'*?<>|+_ ]"
    declare -r REPLACEMENT_STRING="-"
    
    # Obtain the POSIX path of each selected item in the `Finder`. Input must
    # passed to this script via a preceding `Get Selected Finder Items` action
    # in an Automator Services workflow.
    declare selected_items=("$@")
    
    declare -a sorted_paths
    declare -a numbered_paths
    
    # Prefix the POSIX path depth level to itself to aid sorting.
    for ((i = 0; i < "${#selected_items[@]}"; i++)); do
      numbered_paths+=("$(echo "${selected_items[$i]}" | \
          awk -F "/" '{ print NF-1, [=10=] }')")
    done
    
    # Sort each POSIX path in an array by descending order of its depth.
    # This ensures deeper paths are renamed before shallower paths.
    IFS=$'\n' read -rd '' -a sorted_paths <<<  \
        "$(printf "%s\n" "${numbered_paths[@]}" | sort -rn )"
    
    
    # Logic to perform replacement of impermissible characters in a path basename.
    # @param: {Array} - POSIX paths sorted by depth in descending order.
    renameBaseName() {
      local paths=("$@") new_basename new_path
    
      for path in "${paths[@]}"; do
    
        # Remove numerical prefix from each $path.
        path="$(sed -E "s/^[0-9]+ //" <<< "$path")"
    
        # Replaces impermissible characters in path basename
        # and subsitutes uppercase characters with lowercase.
        new_basename="$(sed "s/$IMPERMISSIBLE_CHARS/$REPLACEMENT_STRING/g" <<< \
            "$(basename "${path}")" | tr "[:upper:]" "[:lower:]")"
    
        # Concatenate original dirname and new basename to form new path.
        new_path="$(dirname "${path}")"/"$new_basename"
    
        # Only rename the item when:
        # - New path does not already exist to prevent data loss.
        # - New basename length is less than or equal to 255 characters.
        if ! [ -e "$new_path" ] && [[ ${#new_basename} -le 255 ]]; then
          mv -n "$path" "$new_path"
        fi
      done
    }
    
    renameBaseName "${sorted_paths[@]}"
    
  8. Automator Service/Workflow 的已完成 canvas 区域现在应该如下所示:

  9. 键入 ⌘S,或从菜单栏中选择 File > Save。我们将文件命名为 Replace Impermissible Chars.


运行启用服务:

  1. Finder中:

    • Select一个单个文件或文件夹然后ctrl+点击显示上下文菜单
    • Select上下文菜单中的Replace Impermissible Chars服务到运行它。
  2. 或者,在 Finder 中:

    • Select 多个个文件and/or个文件夹,然后ctrl+单击 显示上下文菜单
    • Select上下文菜单中的Replace Impermissible Chars服务到运行它。

备注:

  1. Finder 将允许 运行 一次最多选择 1000 files/folders 个服务。

  2. 如果这是您创建的第一个 Automator 服务,您 可能(如果我没记错的话!)需要重新启动计算机才能使其成为通过上下文弹出菜单可用。

  3. 如果满足以下两个条件之一,Bash Shell 脚本将不会替换 file/folder 名称中不允许的字符:

    • 如果 file/folder 名称已经存在,并且与生成的新 file/folder 名称相匹配。例如:假设我们有一个名为 hello-world.txt 的文件,在同一文件夹中有一个名为 hello?world.txt 的文件。如果我们 运行 hello?world.txt 上的服务,它的名称将不会是 corrected/changed,因为这可能会覆盖已经存在的 hello-world.txt 文件。

    • 如果生成的文件名是>=到255个字符。当然,只有当您将 REPLACEMENT_STRING 值(在 Bash/Shell 脚本中)更改为多个字符,而不只是一个连字符 -.[=35= 时,才会发生这种情况]

这里有两个很好的解决方案。但由于每个问题通常都有很多解决方案,我提供另一个:

    property alphabet : "abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    --------------------------------------------------------------------------------
    rename from "HELLO World+Foo(\"Bar\")new.ext"
    --------------------------------------------------------------------------------
    ### HANDLERS
    #
    # rename from:
    #   Receives a text string and processes it for invalid characters, which
    #   get replaced with the specified replacement string (default: "-"),
    #   returning the result
    to rename from filename given disallowed:¬
        invalidCharSet as text : "[()[\/:\"*?<>|]+_] ", replaceWith:¬
        replacementStr as text : "-"
        local filename
        local invalidCharSet, replacementStr

        set my text item delimiters to {replacementStr} & ¬
            the characters of the invalidCharSet

        text items of the filename as text

        makeLowercase(the result)
    end rename


    # makeLowercase():
    #   Receives a text string as input and returns the string formatted as
    #   lowercase text
    to makeLowercase(str as text)
        local str

        set my text item delimiters to ""

        if str = "" then return ""

        set [firstLetter, otherLetters] to [¬
            the first character, ¬
            the rest of the characters] of str


        tell the firstLetter to if ¬
            it is "-" or ¬
            it is not in the alphabet then ¬
            return it & my makeLowercase(the otherLetters)

        considering case
            set x to (offset of the firstLetter in the alphabet) mod 27
        end considering

        return character x of the alphabet & my makeLowercase(the otherLetters)
    end makeLowercase

此代码可用于 运行 AppleScript Automator 操作,将 rename from... 放在 on run {input, parameters} 处理程序,以及它之外的其余代码。它可以遵循向其提供 Finder 中的文件列表的操作,或者如果它是 运行 作为 [=21],它可以直接从工作流的输入中接收其输入=]服务.

    property alphabet : "abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    on run {input, parameters}
        tell application "Finder" to repeat with f in input
            set the name of f to (rename from f)
        end repeat
    end run

    to rename from ...
        .
        .
    end rename

    to makeLowercase(str as text)
        .
        .
    end makeLowercase