I wrote this for myself. It is fast, allows regex in find and replace, can ignore the file suffix, and can show what would happen in a "trial run" as well as protect against over-writing existing files.
If you are are on a mac, it can use applescript to pick out the current folder in the Finder as a target folder.
umx_rename_file <- function(findStr = "Finder", replaceStr = NA, baseFolder = "Finder", test = TRUE, ignoreSuffix = TRUE, listPattern = NULL, overwrite = FALSE) {
    umx_check(!is.na(replaceStr), "stop", "Please set a replaceStr to the replacement string you desire.")
    # ==============================
    # = 1. Set folder to search in =
    # ==============================
    if(baseFolder == "Finder"){
        baseFolder = system(intern = TRUE, "osascript -e 'tell application \"Finder\" to get the POSIX path of (target of front window as alias)'")
        message("Using front-most Finder window:", baseFolder)
    } else if(baseFolder == "") {
        baseFolder = paste(dirname(file.choose(new = FALSE)), "/", sep = "") ## choose a directory
        message("Using selected folder:", baseFolder)
    }
    # =================================================
    # = 2. Find files matching listPattern or findStr =
    # =================================================
    a = list.files(baseFolder, pattern = listPattern)
    message("found ", length(a), " possible files")
    changed = 0
    for (fn in a) {
        if(grepl(pattern = findStr, fn, perl= TRUE)){
            if(ignoreSuffix){
                # pull suffix and baseName (without suffix)
                baseName = sub(pattern = "(.*)(\\..*)$", x = fn, replacement = "\\1")
                suffix   = sub(pattern = "(.*)(\\..*)$", x = fn, replacement = "\\2")
                fnew = gsub(findStr, replacement = replaceStr, x = baseName, perl= TRUE) # replace all instances
                fnew = paste0(fnew, suffix)
            } else {
                fnew = gsub(findStr, replacement = replaceStr, x = fn, perl= TRUE) # replace all instances
            }
            if(test){
                message(fn, " would be changed to:  ", omxQuotes(fnew))
            } else {
                if((!overwrite) & file.exists(paste(baseFolder, fnew, sep = ""))){
                    message("renaming ", fn, "to", fnew, "failed as already exists. To overwrite set T")
                } else {
                    file.rename(paste0(baseFolder, fn), paste0(baseFolder, fnew))
                    changed = changed + 1;
                }
            }
        }else{
            if(test){
                # message(paste("bad file",fn))
            }
        }
    }
    if(test & changed==0){
        message("set test = FALSE to actually change files.")
    } else {
        umx_msg(changed)
    }
}