0

I'm trying to create a bash script that will move all files recursively from a source folder to a target folder, and simply rename files if they already exist. Similar to the way M$ Windows does, when a file exists it auto-renames it with "<filemame> (X).<ext>", etc. except for ALL files.

I've create the below, which works fine for almost all scenarios except when a folder has a (.) period in its name and a file within that folder has no extension (no period in its name).

eg a folder-path-file such as: "./oldfolder/this.folder/filenamewithoutextension"

I get (incorrectly): "./newfolder/this (1).folder/filenamewithoutextension"

if "./newfolder/this.folder/filenamewithoutextension" already exist in the target location (./newfolder),

instead of correctly naming the new file: "./oldfolder/this.folder/filenamewithoutextension (1)"


#!/bin/bash
source=$1 ; target=$2 ;
if [ "$source" != "" ] && [ "$target" != "" ] ; then
        #recursive file search
        find "$source" -type f -exec bash -c '
                #setup variables
                oldfile="$1" ; osource='"${source}"' ; otarget='"${target}"' ;
                #set new target filename with target path
                newfile="${oldfile/${osource}/${otarget}}" ;
                #check if file already exists at target
                [ -f "${newfile}" ] && {
                        #get the filename and fileextension for numbering - ISSUE HERE?
                        filename="${newfile%/}" ; newfileext="${newfile##*.}" ;
                        #compare filename and file extension for missing extension
                        if [ "$filename" == "$newfileext" ] ; then 
                               #filename has no ext - perhaps fix the folder with a period issue here?
                               newfileext="" ; 
                        else 
                               newfileext=".$newfileext" ; 
                        fi
                        #existing files counter
                        cnt=1 ; while [ -f "${newfile%.*} (${cnt})${newfileext}" ] ; do ((cnt+=1)); done
                        #set new filename with counter - New Name created here *** Needs re-work, as folder with a period = fail
                        newfile="${newfile%.*} (${cnt})${newfileext}";
                }
                #show mv command
                echo "mv \"$oldfile\" \"${newfile}\""
        ' _ {} \;
else
        echo "Requires source and target folders";
fi

I suspect the issue is, how to properly identify the filename and extension, found in this line:

filename="${newfile%/}" ; newfileext="${newfile##*.}" which doesn't identify a filename properly (files are always after the last /).

Any suggestion on how to make it work properly?

UPDATED: Just some completion notes - Issues fixes with:

  1. Initially Splitting each full path filename: path - filename - (optional ext)
  2. Reconstructing the full path filename: path - filename - counter - (optional ext)
  3. fixed the file move to ensure directory structure exists with mkdir -p (mv does not create new folders if they do not exist in the target location).
jkeys
  • 113
  • 8
  • Using `basename` instead of string manipulation would probably work – Aaron Aug 05 '21 at 12:23
  • See [Extract filename and extension in Bash](https://stackoverflow.com/q/965053/6770384). – Socowi Aug 05 '21 at 12:25
  • Btw: To save yourself from quoting-hell you can use `myfunc() { yourCodeHere; }; export -f myfunc; export source target; find ... -exec bash -c 'myfunc "$@"' _ {} \;` or ( if your `find` has the `-print0` option) `find ... -print0 | while IFS= read -rd '' oldfile; yourCodeHere; done`. – Socowi Aug 05 '21 at 12:29
  • don't know how this was marked as already having answers from another post - as I said, "I suspect" it was the filename extract issue - well its not the issue. The issue is the new filename *construction* after the 'While' loop, which creates the new name with a counter # = its failing the filename construction with the counter # - I'll resolve it – jkeys Aug 05 '21 at 12:54

1 Answers1

1

Maybe you could try this instead?

filename="${newfile##*/}" ; newfileext="${filename#*.}"
  • The first pattern means: remove the longest prefix (in a greedy way) up to the last /.
  • The second one: remove the prefix up to the first dot (the greedy mode seems unnecessary here) − and as you already noted, in case the filename contains no dot, you will get newfileext == filename

Example session:

newfile='./oldfolder/this.folder/filenamewithoutextension'
filename="${newfile##*/}"; newfileext="${filename#*.}"
printf "%s\n" "$filename"
#→ filenamewithoutextension
printf "%s\n" "$newfileext"
#→ filenamewithoutextension

newfile='./oldfolder/this.folder/file.tar.gz'
filename="${newfile##*/}"; newfileext="${filename#*.}"
printf "%s\n" "$filename"
#→ file.tar.gz
printf "%s\n" "$newfileext"
#→ tar.gz
ErikMD
  • 13,377
  • 3
  • 35
  • 71
  • Yes that gets the filename correctly. filename="${newfile##*/}" ; . I'll have a look at after the counter portion (while loop), as its constructing the new name target name with a counter incorrectly - which appears to be also an issue. – jkeys Aug 05 '21 at 12:46
  • OK (BTW note that the `newfileext="${newfile##*.}"` needed to be changed also as I had suggested: you need to reuse the `filename` variable in `newfileext="${filename#*.}"`, not `$newfile`, otherwise you'll get the dot issue); regarding the remaining issue you mention, I guess the culprit is the variable `${newfile%.*}` which has several occurrences, and which should be changed in a similar way… – ErikMD Aug 05 '21 at 13:15
  • 1
    Yep, done and done. Also I missed that mv does not make missing diretories, so I had to add mkdir -p before each file mv. Working now. Thanks for your assistance :) – jkeys Aug 05 '21 at 15:01