Bash – Rename multiple files, removing all but one instance of a pattern

awkbashrenamesed

There has to be a simple solution for my problem, but I can't get it.
I have multiple files in multiple folders, whose names have a pattern repeated multiple times in a row, like this:

20170223_LibError.log-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-XYZ12-SAE066.log_compressed_at_2017-09-27_03-32-55.gz

I need to remove all but one XYZ12 of the patterns from the file names, to get the following result:

20170223_LibError.log-XYZ12-SAE066.log_compressed_at_2017-09-27_03-32-55.gz

Best Answer

a) find + prename(Perl rename) solution:

find . -type f -name "*-XYZ12-XYZ12-*.gz" -exec prename 's/(-XYZ12)(\1)+/$1/g' {} \;

b) Additional bash + find + sed approach if prename is not supported:

for f in $(find . -type f -name "*-XYZ12-XYZ12-*.gz"); do 
    p="${f%/*}"      # full path without basename (parent folders)   
    fn="${f##*/}"    # current filename (basename)
    new_fn=$(sed 's/\(-XYZ12\)\+/-XVZ12/' <<<"$fn")  # new file name
    mv "$f" "$p/$new_fn"
done

c) Also, you are able to avoid using sed in the above bash approach by using just bash variable substitution:

shopt -s extglob
for f in $(find . -type f -name "*-XYZ12-XYZ12-*.gz"); do 
    p="${f%/*}"      # full path without basename (parent folders)   
    fn="${f##*/}"    # current filename (basename)
    new_fn="${fn/+(-XYZ12)/-XVZ12}"  # new file name
    mv "$f" "$p/$new_fn"
done
Related Question