Andrew Mercer
on this page

Test first. Put echo in front of mv, read the output, and then remove echo. Use mv -i or mv -n when unsure.

Bash parameter expansion (no extra tools)

Pattern Effect
${f%.txt} remove the shortest match of .txt from the end
${f#prefix} remove from the start
${f/old/new} replace the first old
${f//old/new} replace all old
${f/#abc/def} replace abc only at the start
${f/%abc/def} replace abc only at the end
# Change extension .txt -> .json
for f in *.txt; do mv -- "$f" "${f%.txt}.json"; done

# Change .base -> .rhel6  (preview first)
for f in *.base; do echo mv -- "$f" "${f%.base}.rhel6"; done
for f in *.base; do mv -- "$f" "${f%.base}.rhel6"; done

# Remove "_dev" anywhere in the name
for f in *_dev*; do mv -- "$f" "${f//_dev/}"; done

# Replace only the start of a name
for f in Project-Write*.txt; do mv -- "$f" "${f/#Project-Write/Writing-Journal}"; done
# abc01.txt abc02.txt -> def01.txt def02.txt
for f in abc*.txt; do mv -- "$f" "${f/#abc/def}"; done

# Rename .yml -> .yaml everywhere below a directory
find . -name '*.yml' -exec sh -c 'for f; do mv -- "$f" "${f%.yml}.yaml"; done' _ {} +

# Strip the last 20 characters (like a timestamp suffix): cut with substring expansion
for f in *; do mv -i -- "$f" "../out/${f::${#f}-20}"; done
# Example: import2kair.11.41.5499.C.out_2013-06-12_12-06-37 -> import2kair.11.41.5499.C.out

Loops over * (not $(ls)) handle spaces in names correctly.

Using sed for the replacement

for f in *triggers*; do mv -- "$f" "$(echo "$f" | sed 's/triggers/trigger/g')"; done
# Change a year in the names
for f in *2012*; do mv -- "$f" "$(echo "$f" | sed 's/2012/2013/')"; done

rename: there are two different commands

  • Perl rename (Debian/Ubuntu: rename or prename; Fedora: prename from perl-File-Rename) takes a Perl expression:
rename -n 's/\(1982\) - //; s/ - \(480p\)//' *.mkv        # -n = dry run
rename 's/ - \(480p\)//' *.mkv

Turning Show (1982) - S08E01 - (480p).mkv into Show - S08E01.mkv is a typical use.

  • util-linux rename (Fedora/RHEL rename) uses a plain-text form: rename <from> <to> <files>:
rename ' - (480p)' '' *.mkv
rename -v .base .rhel6 *.base

Check which you have: rename --version (util-linux prints "util-linux"). Scripts that use the wrong dialect fail or, worse, misnamed files. See also find -execdir and mv.