# Rename dirs to `snake_case` and files to `PascalCase`. Careful with structured file names like archives! # Usage: name [FILES] function name() { local IFS=$'\n' local targets=(${@}) [[ ${targets} == "" ]] && targets=($(ls)) process() { # Skip archive. if $(_is_archive "${target}"); then _iterate_skip "File is an archive, skip." return 0 fi if [[ -d ${target} ]]; then local new_name=$(parse_snake ${target}) [[ -e ${new_name} ]] && return 0 mv -- ${target} ${new_name} && echo ${new_name} else local ext=".${target##*.}" local name=${target%.*} [[ ${ext} == ".${target}" ]] && ext="" local new_name="$(parse_pascal ${name})${ext}" [[ -e ${new_name} ]] && return 0 mv -- ${target} ${new_name} && echo ${new_name} fi } _iterate_targets process ${targets[@]} } # Rename files with provided parser, i.e. `parse_simple`. # All files by default. # Usage: name_parse [FILES] function name_parse() { local IFS=$'\n' local parser=${1} local targets=(${@:2}) [[ ${targets} == "" ]] && targets=("[^.]*") if [[ ${parser} == "" ]]; then help name_parse return 2 fi process() { # Skip archive. if $(_is_archive "${target}"); then _iterate_skip "File is an archive, skip." return 0 fi # parse new name. local ext="" local name="${target}" # ext only for files. if [[ -f ${target} ]]; then ext=".${target##*.}" name="${target%.*}" fi # Files w/o extension support. [[ ${ext#.} == "${name}" ]] && ext="" # Get new name. local new_name=$(${parser} "${name}")${ext,,} # check if same name. [[ ${target} == "${new_name}" ]] && return 0 # check if target name already exists. if [[ -f ${new_name} ]]; then _error "${new_name}: Already exists!" return 1 fi # rename target. mv -- "${target}" "${new_name}" && echo "${new_name}" } _iterate_targets process ${targets[@]} } # Rename all files to their hashes while keeping extensions. # All files by default. # Usage: name_hash [FILES] function name_hash() { local IFS=$'\n' local targets=(${@}) [[ ${targets} == "" ]] && targets=($(_ls_file)) process() { # extract extension. local extension="${target##*.}" if [[ ${extension} == "${target}" ]]; then extension="" else extension=".${extension}" fi # hash the new name. local hash=$(pv "${target}" | sha1sum | cut -d\ -f1) new_name="${hash,,}${extension,,}" # check if same name. [[ ${target} == "${new_name}" ]] && return 0 # rename target. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Check hashes for previously renamed files. # All files by default. # Usage: name_hash_check [FILES] function name_hash_check() { local IFS=$'\n' local targets=(${@}) [[ ${targets} == "" ]] && targets=("[^.]*") process() { # extract hashes. local stored="${target%%.*}" local actual=$(pv "${target}" | sha1sum | cut -d\ -f1) # compare hashes. if [[ ${stored} != "${actual}" ]]; then _error "Failed." return 1 fi } _iterate_targets process ${targets[@]} } # Rename files for Jellyfin shows, i.e. `Episode S01E01.mkv` # All files by default. # Usage: name_show [FILES] function name_show() { local IFS=$'\n' local season="$(realpath .)" season="${season##*\ }" local episode=0 local targets=(${@}) [[ ${targets} == "" ]] && targets=($(_ls_file)) # Error when no season number specified. if [[ ${season} == "" ]]; then _error "Could not determine season number." return 2 fi process() { ((episode++)) # extract new name. local new_name="Episode S${season}E$(printf %02d ${episode}).${target##*.}" # Skip on no change. [[ ${target} == "${new_name}" ]] && return 0 # rename target. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Rename files for Kavita manga format. # All files by default. # Usage: name_manga [FILES] function name_manga() { local IFS=$'\n' local manga=${PWD##*/} local season=${1} local episode=0 local targets=(${@:2}) [[ ${targets} == "" ]] && targets=($(_ls_file)) # Error when no season number specified. if [[ ${season} == "" ]]; then help name_manga return 2 fi process() { ((episode++)) # Extract new name. local new_name="${manga} Vol.${season} Ch.${episode}.${target##*.}" # Skip on no change. [[ ${target} == "${new_name}" ]] && return 0 # Rename target. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Rename files for ffmpeg_music_meta format. # All files by default. # Usage: name_music [FILES] function name_music() { local IFS=$'\n' local targets=(${@}) [[ ${targets} == "" ]] && targets=($(ls)) process() { # Extract new name. local ext=${target##*.} if [[ -d ${target} ]]; then local new_name="$(parse_startcase $(parse_simple ${target%.*}))" else local new_name="$(parse_startcase $(parse_simple ${target%.*})).${ext}" fi # Skip on no change. [[ ${target%/} == "${new_name}" ]] && return 0 # Rename target. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Rename files with new extension. # All files by default. # Usage: name_ext [FILES] function name_ext() { local IFS=$'\n' local extension=${1} local targets=(${@:2}) [[ ${targets} == "" ]] && targets=($(_ls_file)) # Error when no new extension specified. if [[ ${extension} == "" ]]; then help name_ext return 2 fi process() { # Extract new name. local new_name="${target%.*}"."${extension}" # Skip on no change. [[ ${target} == "${new_name}" ]] && return 0 # Rename target. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Change file name prefix. # All matching files by default. # Usage: name_prefix [FILES] function name_prefix() { local IFS=$'\n' local old=${1} local new=${2} local targets=(${@:3}) [[ ${targets} == "" ]] && targets=(${old}*) process() { # Create new name. local new_name="${new}${target#$old}" # Skip on no change. [[ ${target} == "${new_name}" ]] && return 0 # Rename. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Change file name postfix. # All matching files by default. # Usage: name_postfix [FILES] function name_postfix() { local IFS=$'\n' local old=${1} local new=${2} local targets=(${@:3}) [[ ${targets} == "" ]] && targets=(*${old}) process() { # Create new name. local new_name="${target%$old}${new}" # Skip on no change. [[ ${target} == "${new_name}" ]] && return 0 # Rename. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Replace part of the name. # All matching files by default. # Usage: name_replace [FILES] function name_replace() { local IFS=$'\n' local old=${1} local new=${2} local targets=(${@:3}) [[ ${targets} == "" ]] && targets=(*${old}*) process() { # Create new name. local new_name="${target//$old/$new}" # Skip on no change. [[ ${target} == "${new_name}" ]] && return 0 # Rename. mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } # Fix numbering for numbered files. I.e if there are 10 items and some of them start without zero, then append zero to it. 1..10 -> 01..10. # Usage: name_fix_numbering [FILES] function name_fix_numbering() { local IFS=$'\n' local highest=0 local power=0 local targets=(${@}) [[ ${targets} == "" ]] && targets=($(ls | grep "^[0-9]")) # Count leading zeroes. for target in "${targets[@]}"; do # Check that starts with a digit. [[ ${target} =~ ^[0-9] ]] || continue local digits=($(parse_ints "${target}")) local digit="${digits[0]}" digit=$((10#${digit})) [[ ${digit} -gt ${highest} ]] && highest="${digit}" done local i=${highest} while [[ i -gt 0 ]]; do ((power++)) i=$((i / 10)) done process() { # Check that starts with a digit. if [[ ! ${target} =~ ^[0-9] ]]; then _error "Does not start with a digit!" return 1 fi # Prepare new name. local digits=($(parse_ints "${target}")) local digit="${digits[0]}" digit=$((10#${digit})) local new_name=$(printf "%0${power}d" "${digit}")"${target#${digits[0]}}" # Skip if the same name. [[ ${target} == "${new_name}" ]] && return 0 # Check that file does not exist. if [[ -e ${new_name} ]]; then _error "${new_name}: File exists!" return 1 fi mv -- ${target} ${new_name} && echo ${new_name} } _iterate_targets process ${targets[@]} } function _comp_name_parse() { _autocomplete $(find_function | grep ^parse) } complete -o filenames -F _comp_name_parse name_parse