find . -type f -name '*authors-name-string*' -exec bash -c 'mv "$0" "${0/authors-name-string/}"' {} \;

I am trying to rename files nested in several layers of directories that contain an author's name text string embedded somewhat randomly in the filename. Creating a new filename that starts with the author's name followed by the rest of the original filename ... but sans the authors name string that was located in a somewhat random location in the original filename.

The shell command line above will recursively remove the authors-name-text string from the filename, but I have not figured out how to do that AND prepend the authors-name-text (and a trailing "_" underscore) to the start of the filename. Basically I want to move the authors-name-text from wherever it is currently in the filename to being at the beginning of the filename (plus an underscore). As far as I can tell the bash version invoked is v4.3 on my Synology NAS. I've logged in via ssh from my MacBook.

Trying to take thousands of files in different directories organized by subject directory name so that some of the files written by the same author inside the directories appear grouped together when viewing a simple directory listing.

find . -type f -name '*authors-name-string*' -exec bash -c 'mv "$0" "authors-name-string_${0/authors-name-string/}"' {} \;

The above command line fails when it tries to insert the "authors-name-string_" at the front of the entire filename pathway. Since there isn't a starting directory by that name the MV command complains, though, it does still remove the author-name-string from it's original location in the filename as desired.

  • Does your system's find command provide an -execdir option? Feb 19 '19 at 13:09
  • @Mark-in-Settle - can you please paste a sample input file name and an sample output file for better understanding.
    – upkar
    Feb 21 '19 at 10:53

Expanding on your approach and leveraging parameter expansion:

export author_string='author1'
export new_string='author1'
find . -type f -name "*${author_string}*" \
  -exec sh -c '
    echo mv -- "$1" "${path}/${new_string}_${before}${after}"' sh {} \;

My assumptions, since I'm not sure I completely understand your question:

You are searching for files whose name contains a fixed string; here, the variable author_string.
You are renaming files everywhere in a directory tree.
The new name of your files will start with a fixed string (it being the same as author_string or not, makes no big difference); here, the variable new_string.
The remainder of the new name of your files will be equal to their old name, except for the author_string part, which you want removed.

In this code:

  • We export our old/new strings to make them available to the shell invoked by find.

  • With an eye on portability, -exec calls sh. $1 is used instead of $0, as in your snippets, because $0 is typically used to set the name of the shell/command.

  • For each file, we split its full path:

    • path="${1%/*}" is the full path with the file name part removed: ${parameter%word} removes a suffix that matches the word pattern;

    • tmp="${1%%${author_string}[^/]*}" catches the part of each file's full path that precedes author_string: ${parameter%%word} removes the longest suffix that matches the word pattern. We use [^/] to ignore any occurrence of author_string as part of directory names;

    • before="${tmp##*/}" captures only the substring that is part of the file name: ${parameter##word} removes the longest prefix that matches the word pattern;

    • after="${1##*${author_string}}" is the part of the file name that comes after author_string;

  • We rename each file.

Note that, if author_string is repeated in a file name, anything in between the occurrences is lost.

Also, bear in mind that you will need to escape (with a \) all the characters in author_string that have a special pattern matching meaning, if you want to match them literally.

Given the directory tree:

$ tree .
├── testdir
│   ├── title2_author1_date.book
│   ├── title3_author1_date.book
│   └── title4_author2_date.book
├── title1_author1_date.book
└── title5_author2_date.book

This code does:

$ export author_string='author1' new_string='author1'; find . -type f -name "*${author_string}*" -exec sh -c 'path="${1%/*}"; tmp="${1%%${author_string}[^/]*}"; before="${tmp##*/}"; after="${1##*${author_string}}"; echo mv -- "$1" "${path}/${new_string}_${before}${after}"' sh {} \;
mv -- ./title1_author1_date.book ./author1_title1__date.book
mv -- ./testdir/title3_author1_date.book ./testdir/author1_title3__date.book
mv -- ./testdir/title2_author1_date.book ./testdir/author1_title2__date.book

Remove echo from the command when you feel you are ready to actually rename files.


Parameter Expansion in "The Open Group Base Specifications Issue 7, 2018 edition" (POSIX.1-2017)

  • Thank you very very much. After some time spend learning from your approach to the problem and your code, then testing on samples and finally using your code on my target directory structure I can say it worked very well indeed and saved many hours. There were only a few minor rename quirks involving a very small number of files. The issue involved unanticipated residual "_" underscore characters in the new filename. Because you gave me a better understanding of shell commands I have the confidence to learn more and adjust the code to handle these few special cases. Feb 20 '19 at 9:55
  • For the benefit of anyone wanting to use fra-san's answer, the tiny number of "unanticipated residual _ underscore quirks in the filenames were entirely of my own making in the original filename choices and cosmetic. The code did not insert any incorrectly. Example: a few filenames used the author-name-string preceded by an _ at the end of the filename, so when the shell script moved author-name to the front of the new filename successfully, I had not thought in that case it would leave a dangling _ underscore which looked odd. So I will code for those few special cosmetic cases. Feb 20 '19 at 10:25
  • Small Update -- when I tried using the shell command line on one of my smaller Synology NAS it did not work. Testing revealed that particular shell was including the apostrophe ‘ characters used in the export author_string=‘author1’ ; export new_string='author1' commands. When I substituted export author_string=“author1” with “ double quote marks corrected the problem, so that echo ${author_string} => author1 resulted in only the author1 string without quotemarks. Feb 21 '19 at 1:40
  • @Mark-in-Seattle Probably a copy/paste issue: the single quote character accepted by the shell is ASCII 39 (hexadecimal: 27), as are those in new_string='author1'. The quotes in the author_string=‘author1’ string you pasted are U+2018 (hexadecimal: e2 80 98) and U+2019 (hexadecimal: e2 80 98) and they probably come from some text editor.
    – fra-san
    Feb 21 '19 at 7:11

Sounds like a job for Larry Wall's rename. Beware there are other commands with the same name, this one is written in purl.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.