MacOS – Insert Character on batch folder rename

applescriptautomatormacosterminal

I have a folder that has several hundreds of thousands of files named like "ABC 123456". I need to add a dash after between the two blocks of text like this "ABC – 123456". Leaving the trailing space.

Automator does not offer a method for counting 4 characters in and inserting an new dash and space.

I have this terminal command and have been tinkering with it.

for file in *.txt
do
  echo mv "$file" **"$file" count 3 "- "**
done

Anyone know of an Applescript or can help with the terminal command that will help me rename the files?

Best Answer

The question itself isn't exact too, because the OP talks about:

  • files names like ABC 123456 (without extension), but in the example code using *.txt extension
  • add a dash between two blocks (e.g. where is the space) like ABC - 123456, but in the next taking about counting to 4 and count 3, e.g. not exacltly clear what the script should do for example if find ABCD 12 or AB 345
  • also, want keep the trailing space, what is strange - but OK ;)

The problem is dividable to 3 separate parts:

  1. selecting the right files for the rename
  2. preparing the new filename, replacing/adding characters into the old one
  3. the "physical" rename

Ad "selecting the right files". Selecting the files, can be done by some external utilities, for example by the find command. The main benefits of the find are:

  • it can search files recursively in the sub directories too
  • is possible more precisely select the right files, e.g. exclude directories, select files by time or size and so on. (check from the terminal man find.)

Ad "prepare the new name". This can be done by external programs, like sed or awk or any program what can manipulate text in shell scripts, or it can be done (in some simple cases such this) withing the bash itself - and is possible to save one expensive command execution. (for hunderts of thousand files it makes a difference).

The following:

${file/ /-}

substitutes (replaces) one space (the / / part) with the dash (/-). So it is possible to write

mv "$file" "${file/ /-}"

and save the sed execution.

So one of the alternative solutions could be, for example the following:

#!/bin/bash
while IFS= read -r -d $'\0' filename
do
    newfilename="${filename/ /-}"
    [[ "$filename" != "$newfilename" ]] && echo mv -i "$filename" "$newfilename"
done < <(find . -maxdepth 1 -type f -iname "* *.jpg" -print0)

The abobe is for the DRY RUN - it only will show, what will be done, for the real execution remove the echo.

Decomposition:

  • selecting the right files for the rename: the find . -maxdepth 1 -type f -iname "* *.jpg" -print0 will find all

    • what are only in the current directory (-maxdepth 1)
    • and they're plain files (-type f)
    • and their name matches (anything)(space)(anything).jpg case insensitively - e.g. the name must contains a space
    • supply the filenames as null terminated, so their name can safely contain newline character too. (-print0)
  • the cycle while IFS= read -r -d $'\0' filename; do ... done < <( )

    • reads the output from the above find command
    • where the filenames are null terminated (-d $'\0')
    • ignore any escaped characters (-r) /advanced topic - not needed to explained here/
    • the IFS= prevents trimming of leading and trailing whitespace from the filename (also, a bit advanced topic)
  • preparing the new filename newfilename="${filename/ /-}"

    • is done by bash internally (without executing an external command).
    • if want preserve the trailing space after the dash use ${filename/ /- }
  • the actual renaming is done by the mv command (see man mv from the terminal), where

    • the -i what will ask the user if here are already a file with the new filename (don't override it)
  • and the mv is executed only when the newfilename is different from the filename [[ "$filename" != "$newfilename" ]]

The above solution is mostly error-prone, but it still isn't very effective, because for hundreds of thousand files will execute hundreds of thousand times the mv command. The solution can be: using some utility, what can read the filenames and do the renaming without executing the mv command N-times. For example, the "big gun" of system admins the "perl", as:

find . -maxdepth 1 -type f -iname "*.jpg" -print0 |\
    perl -0nle '$n=$_; $n=~s/ /-/; rename $_,$n unless($_ eq $n || -e $n)'

what will rename all files what outputs the find in one execution - e.g. much faster as thousands of mv executions.

Tor testing (DRY RUN) use the following:

find . -maxdepth 1 -type f -iname "*.jpg" -print0 |\
    perl -0nle '$n=$_; $n=~s/ /-/; print qq{old:$_ new:$n\n} unless($_ eq $n || -e $n)' #print instead of the rename

Ps: the perl is powerful enough to handle everything itself, eg. the find command too, but it is more advanced topic...

Ps2: my English is much worse as my bash, so someone could kindly edit this post for adding/correcting things.. ;)