Search code examples
javascriptregexapplescriptautomator

Replacing invalid characters in the filename with dashes using AppleScript


My goal is to create service in Automator using AppleScript or Javascript which replaces all invalid characters of selected filename ()[\\/:"*?<>|]+_ and spaces with dashes (-) and make filename lowercase.


Solution

  • The replacement of impermissible characters in a file/folder name can be achieved by utilizing a Bash Shell script in your Automator Service.

    The following steps describe how to achieve this:

    Configuring Automator

    1. Launch Automator
    2. Type ⌘N, or choose File > New from the Menu bar.
    3. Select Service and click Choose
    4. At the top of the canvas area configure its settings as follows:

      enter image description here

    5. Select Library at the top of the panel/column on the left:

      • In the search field type: Get Select Finder items and drag the Get Select Finder items action into the canvas area.

      • In the search field type: Run Shell and drag the Run Shell Script action into the canvas area.

    6. Configure the top part of the Run Shell Script action as follows:

      enter image description here

    7. Add the following Bash script to the main area of the Run shell Script action:

      #!/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, $0 }')")
      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. The completed canvas area of your Automator Service/Workflow should now appear something like this:

      enter image description here

    9. Type ⌘S, or choose File > Save from the Menu bar. Let's name the file Replace Impermissible Chars.


    Running the Service:

    1. In the Finder:

      • Select a single file or folder and then ctrl+click to show the contextual menu
      • Select the Replace Impermissible Chars service in the contextual menu to run it.
    2. Alternatively, in the Finder:

      • Select multiple files and/or folders and then ctrl+click to show the contextual menu
      • Select the Replace Impermissible Chars service in the contextual menu to run it.

    Notes:

    1. The Finder will allow the Service to be run on up to 1000 files/folders selected at once.

    2. If this is the first Automator Service you have created you may (if I've remembered correctly!) need to restart your computer for it to become available via the contextual pop-up menu.

    3. The Bash Shell script will not replace impermissible characters in a file/folder name if either of the following two conditions are met:

      • If a file/folder name already exists which matches the name of a resultant new file/folder name. For example: Let's say we have a file named hello-world.txt and in the same folder a file named hello?world.txt. If we run the Service on hello?world.txt, it's name will not be corrected/changed as this would potentially overwrite the hello-world.txt file which already exists.

      • If the resultant file name is >= to 255 characters. This may of course only occur if you were to change the REPLACEMENT_STRING value (in the Bash/Shell script) to more than one character, instead of just a single hyphen -.