3

I have hundreds of files with invalid symbols for OneDrive (\/:*?"<>|) and I want to automatically rename them on my Mac so they can save up to the cloud.

  • OneDrive for macOS does not seem to have the auto-renaming option, it just throws an error and bypasses those files for upload.

I looked at the command zmv, but so far, I have no success. I managed to rename all spaces to _ with zmv '(*)' '${1/ /_}', but that's actually not helping. Most of these special character don't work with zmv, even if I escape them with \.

I am on macOS Ventura 13.4 and OneDrive 23.114.0530.0001. I backed up E-Mails from some years and their header is automatically used as file name. My problem files contain lots of these characters like:

This is an "E-Mail".eml
Is this an E-Mail?.eml
This is also an *E-Mail*.eml

Does anyone have an idea how I can get rid of these characters without having to rename these ~650 files manually?

2
  • You did not show your zmv commands for renaming the special characters, so it's hard to say what you did wrong. Commented Jul 6, 2023 at 9:25
  • Perhaps it is easier in this case to loop over the files, and do a mv over each file. Normal parameter substitution should work here, but I would use ${var//.../..} instead of ${var/.../...}, because you want to replace ALL occurances. Commented Jul 6, 2023 at 9:28

4 Answers 4

3

I use a Hazel rule to change "weird" characters in names of files created by "printing" web pages to PDF. It looks for particular characters in file names and modify those to something less problematic. Whilst not specifically aimed at OneDrive files it can easily to modified.

Here is my rule:

Hazel rule

Hazel works by watching folders and applying rules to files whenever added. Each rule consists of a test and a series of actions. The Rename action is sufficiently complex to allow for changing specific characters.

I am in no way claiming this is better than the impressive answer from @bmike. Rather I am presenting it as a very different solution which may appeal to some people.

1
  • OMG hazel is the thing for this - sooo powerful - we couldn't afford to license it in bulk. Much better answer than the script I posted.
    – bmike
    Commented Jun 22, 2023 at 14:57
2

We didn't find a good commercial product (or anything from Microsoft) to do this out of the box, but I do have excellent experience with a script to fix all three big issues we have with OneDrive files:

  • leading spaces in the file name
  • trailing characters that can't exist in OneDrive
  • illegal characters for OneDrive

https://github.com/soundsnw/mac-sysadmin-resources/blob/master/scripts/fix-onedrive-filenames-apfs.sh

Please be sure you have a complete backup before attempting this script. It is well engineered, and does a bunch of checks before it runs, so it's relatively safe even though it's far too long for a beginner to analyze for safety.


This script assumes you use Jamf but there are only 4 lines where the /usr/local/jamf program is called to notify you of progress or an error - you should comment those out with a # at the beginning of each notification line.

The lines I speak of are: 199, 234, 287, and 291

3
  • 1
    Wow, that's an impressive script :D. I thought this would be easier. Thanks. I'll take a look at this.
    – gernophil
    Commented Jun 21, 2023 at 17:57
  • Does this still work when ~/OneDrive is an alias pointing to ~/Library/CloudStorage/OneDrive-Personal? Not hard to change if needed.
    – Gilby
    Commented Jun 22, 2023 at 0:10
  • 1
    @gernophil The tr command, which swaps one character with another, is essentially all you need, and is at the core of this script. You could put this script (or a simpler one) in an Automator Quick Action, or Shortcut, so that you can execute it from the Finder.
    – benwiggy
    Commented Jun 22, 2023 at 10:31
1

You can use zmv, it's just that some of the escapes are a bit unusual. This will replace all of the spaces and special characters in the names of files in the current directory with _:

autoload -Uz zmv
q='"'
zmv -nv '*[ \\:\*\?"<>\|]*' '${f//[ \\:\*\?${q}<>\|]/_}'

Some of the pieces:

  • -n - no-op. zmv will report the planned changes but not execute them.
    Remove the -n when you're ready to actually rename the files.
  • -v - verbose.
  • *[...]* - source pattern. This will match any file that has one of the characters in the [...] character set.
    • [...] - glob pattern to select one of the matching characters.
    • \\:\*\?"<>\| - the characters.
    • \\, \*, \?, \| - these characters have special meanings in glob patterns and need to be escaped.
    • , :, ", <, > - characters that do not need to be escaped.
  • '${f//[ \\:\*\?${q}<>\|]/_}' - destination pattern.
    • ${f...} - f is set by zmv to the source filename.
    • ${f//.../_} - expansion that replaces all matches of a glob pattern with _.
    • [ \\:\*\?${q}<>\|] - the same list of characters as before, with one significant difference. The (e) parameter expansion flag that zmv uses apparently cannot handle double quotes in a pattern, but it can accept variables that contain double quotes. Therefore this uses ${q}, which was set to ", instead of referencing " directly.

Note that the patterns do not include /, i.e. a forward slash. There really isn't a way in unix shell to have a forward slash in a filename, although there is a way to have visible forward slashes in Finder.


Edited to add a more comprehensive zmv example.

This will rename the regular files in the current directory and subdirectories. The new filenames will have:

  • leading whitespace trimmed.
  • other consecutive whitespace characters replaced with a single _, e.g. aa<space><tab><cr>bb will become aa_bb.
  • special characters updated based on the mapping in the associative array.
#!/usr/bin/env zsh
local -A map=(
  ['<']='('    ['>']=')'    ['{']='('    ['}']=')'
  ['*']='.'    ['?']='.'    ['"']='-'    ["'"]='-'
  ['\']='-'    [':']='-'    ['|']='-'
)
local ch=${(kj..)map}
newName() {
  setopt histsubstpattern extendedglob localoptions
  fn=${${1##[[:space:]]##}//[[:space:]]##/_}
  print -r -- ${fn:gs/(#b)([$ch])/$map[$match[1]]}
}
autoload -Uz zmv
zmv -nv "(**/)(*[[:space:]$ch]*)(#q.)" '$1$(newName $2)'
0

How about this:

for f in *
do
    # Replaces * " '  by an underscore.
    # Please adapt to your needs.
    new_f=${f//[*\"\']/_}
    if [[ $f != $new_f ]]
    then
      if [[ -e $new_f ]]
      then
        echo Can not rename $f, because we already have a file $new_f
      else
        mv -c $f $new_f
      fi
    fi
 done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .