In a large drive, I have multiple hundreds of files each named differently but with a few regular expressions. Each appended with random numbers and sometimes characters.

For example, there are 30 files named with a variation of the word "television", and another 50 varied from "lightbulb".

The thing is, that due to the bad coding practice I had years ago when implementing the script that generated the names, it's very inconsistent; so we might get:

  • television139443.png
  • elevision244904.png
  • televisio097798.png
  • elevisio984882.png
  • _televisi90890890.png
  • television-911181.png

You get the idea. That pattern applies to all the file "categories" -- "television", "lightbulb", "motorcar", etc. Luckily, there are at least 5 consistent characters in each filename which are not repeated in the other categories (a dozen cats).

What I'd like to achieve is to recursively go through the mixed folders, rename each file with the proper full title + appending creation date, and move them too their respective folder.

Sort of like:

   case : regex("levis"):
rename to Television-($creation_date).($extension)
mv to ~/Categories/Television/
    case : regex("ghtbu"):
rename to Lightbulb-($creation_date).($extension)
mv to ~/Categories/Lightbulbs/

Obviously this isn't proper code; it's just to illustrate the idea. I'm decently comfortable for everyday bash/zsh tasks but not fluent enough!

I would also need to gracefully handle cases where no regex is matched.

  • Are these 5 consistent characters always the same? Like levis for television, ghtbu for lightbulb, etc.
    – user309777
    Nov 30, 2019 at 22:23
  • Yes, it doesn't need to search for possible variations, i can easily make a list in which 99% of all files will match Nov 30, 2019 at 22:25
  • Well, you can select each group using find then; and with an -exec, you can rename those files. This is either really trivial, or I'm missing a point
    – user309777
    Nov 30, 2019 at 22:30
  • You can't have creation date. You can have the date that the file was last modified, though. Nov 30, 2019 at 22:34
  • What should happen if you get two files with the same canonicalised name and date? Nov 30, 2019 at 22:35

2 Answers 2


With zsh, you could use a different approach based on its approximate matching feature:

autoload zmv # best in ~/.zshrc
zmodload zsh/stat

categories=(television lightbulb motorcar etc)
mkdir -p -- $dest/${(C)^categories}

zmv -n '(**/)(*[^0-9])<->(.*)' \
          zstat -F %FT%T%z +mtime -- $f)$3'

The ${(M)categories:#(#a3)$2} looks for $2 (the part before the number) in the categories array allowing up to three errors (different character, transposition, insertion, deletion).

Remove the -n (dry-run) if OK.

For your approach, it could be something like:

autoload zmv # best in ~/.zshrc
zmodload zsh/stat

typeset -A categories

  levis Television
  ghtbu Lightbulb
  otorc Motocar

mkdir -p -- $dest/$^categories

for k (${(k)categories}) (
  zmv -n "(**/)*$k*(.*)" '$dest/$categories[$k]/$categories[$k]-$(
          zstat -F %FT%T%z +mtime -- $f)$2'

Or if the common characters are always the 3rd to 7th:

autoload zmv # best in ~/.zshrc
zmodload zsh/stat


categories=(Television Lightbulb Motocar)

mkdir -p -- $dest/$^categories

for c ($categories) (
  zmv -n "(**/)*$c[3,7]*(.*)" '$dest/$c/$c-$(
          zstat -F %FT%T%z +mtime -- $f)$2'
  • Thanks a lot for the effort, can't seem to make it work, keep getting zmv:239: no matches found: */*$k*(.) - any clue where i could go wrong ? (ps: even tried with the same exat code you wrote and a bunch of dummy files), thanks, bonne année ! Jan 2, 2020 at 16:55
  • tried setopt nonomatch no luck Jan 2, 2020 at 17:08
  • 1
    @I'maTIcalculator, sorry, my bad, it looks like I hadn't tested them at the time. There were a few issues. See edit, it should be better now. Jan 3, 2020 at 8:21
  • thank you very much ! here's the final code i'm using if anyone is interested (just the zmv part, rest is standard) : for c ($categories) ( zmv -n "$dest/*$c[3,7]*(.*)" '$dest/$c/$c-${RANDOM}$1' ) crazy the amount of builtin functions zsh has ! also curious why the first part only works with double quotes "" and not single ? Jan 3, 2020 at 10:53

Probably not the most efficient way but I would turn the problem on it's head and match the strings within the filenames to the directories and, since the current enumeration is random, just count them in to maintain uniqueness.

eval homedir=$homedir

cats="Television Lightbulb Bananas Motorcar"
declare -A count

for cat in $cats; do if [ ! -d $homedir/$cat ]; then mkdir -p $homedir/$cat; fi; done

while read f; do
    r=$(grep -Po "[a-z]+" <<<$r)
    for cat in $cats; do
        if [ $(grep -i "$r" <<< $cat) ]; then
            mv "$f" $homedir/$cat/$cat-$(printf "%03g" ${count[$cat]}).${f##*.}
done <<< $(find ./banana/ -type f) 

Non-matches will exit the grep loop silently and be left in place for special attention later on.

Anything else you want to include in the filename composition can go in, last modified date would be

$(stat -c %Y $f)


You must log in to answer this question.

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