Search code examples
bashbatch-rename

bash find exec: rename files and preserve optional extensions?


I want to find all files that match a pattern and append a string to the prefix, preserving the extension if it exists. Some of the files have extensions and others don't.

Desired transform:

tools-win/foo.exe => tools-win/foo_bar.exe
tools-osx/foo     => tools-osx/foo_bar

Is there a way to do this with bash parameter expressions? My current attempt is:

find . -path 'tools-*/*' \
    -execdir sh -c 'mv "$1" "${1%.*}_$2.${1##.}"' _ {} bar \;`

This works on files that have extensions, but captures the entire prefix for files that have no extensions:

tools-win/foo.exe => tools-win/foo_bar.exe
tools-osx/foo     => tools-osx/foo_bar.foo

Is there a single parameter expression I can use here that handles both cases?


Solution

  • The inner shell script can parse out the prefix by stripping everything including and after the final ., and then the suffix by stripping the prefix out of the original string. The final string can be assembled with standard concatenation:

    PREFIX=${1%.*}
    SUFFIX=${1##"${PREFIX}"}
    FINAL=${PREFIX}_bar${SUFFIX}
    

    Note that if ${PREFIX} is identical to the input string, ${SUFFIX} will be empty.

    So while it's not a one-liner inside sh, an invocation that only calls find -exec once without pipes looks like this:

    find "${PATH_TO_SEARCH}" -path "${PATH_TO_SEARCH}/tools-*/*" \
        -execdir sh -c 'p=${1%.*};s=${1##"${p}"};mv "$1" "${p}_$2${s}"' sh {} bar \;`