Batch renaming files with Bash

BashShellFile Rename

Bash Problem Overview


How can Bash rename a series of packages to remove their version numbers? I've been toying around with both expr and %%, to no avail.

Examples:

Xft2-2.1.13.pkg becomes Xft2.pkg

jasper-1.900.1.pkg becomes jasper.pkg

xorg-libXrandr-1.2.3.pkg becomes xorg-libXrandr.pkg

Bash Solutions


Solution 1 - Bash

You could use bash's parameter expansion feature

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

Quotes are needed for filenames with spaces.

Solution 2 - Bash

If all files are in the same directory the sequence

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

will do your job. The sed command will create a sequence of mv commands, which you can then pipe into the shell. It's best to first run the pipeline without the trailing | sh so as to verify that the command does what you want.

To recurse through multiple directories use something like

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh


Note that in sed the regular expression grouping sequence is brackets preceded by a backslash, \( and \), rather than single brackets ( and ).

Solution 3 - Bash

I'll do something like this:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

supposed all your file are in the current directory. If not, try to use find as advised above by Javier.

EDIT: Also, this version don't use any bash-specific features, as others above, which leads you to more portability.

Solution 4 - Bash

We can assume sed is available on any *nix, but we can't be sure it'll support sed -n to generate mv commands. (NOTE: Only GNU sed does this.)

Even so, bash builtins and sed, we can quickly whip up a shell function to do this.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Usage

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Creating target folders:

Since mv doesn't automatically create target folders we can't using our initial version of sedrename.

It's a fairly small change, so it'd be nice to include that feature:

We'll need a utility function, abspath (or absolute path) since bash doesn't have this build in.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Once we have that we can generate the target folder(s) for a sed/rename pattern which includes new folder structure.

This will ensure we know the names of our target folders. When we rename we'll need to use it on the target file name.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Here's the full folder aware script...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Of course, it still works when we don't have specific target folders too.

If we wanted to put all the songs into a folder, ./Beethoven/ we can do this:

Usage

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Bonus round...

Using this script to move files from folders into a single folder:

Assuming we wanted to gather up all the files matched, and place them in the current folder, we can do it:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Note on sed regex patterns

Regular sed pattern rules apply in this script, these patterns aren't PCRE (Perl Compatible Regular Expressions). You could have sed extended regular expression syntax, using either sed -r or sed -E depending on your platform.

See the POSIX compliant man re_format for a complete description of sed basic and extended regexp patterns.

Solution 5 - Bash

Here is a POSIX near-equivalent of the currently accepted answer. This trades the Bash-only ${variable/substring/replacement} parameter expansion for one which is available in any Bourne-compatible shell.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

The parameter expansion ${variable%pattern} produces the value of variable with any suffix which matches pattern removed. (There is also ${variable#pattern} to remove a prefix.)

I kept the subpattern -[0-9.]* from the accepted answer although it is perhaps misleading. It's not a regular expression, but a glob pattern; so it doesn't mean "a dash followed by zero or more numbers or dots". Instead, it means "a dash, followed by a number or a dot, followed by anything". The "anything" will be the shortest possible match, not the longest. (Bash offers ## and %% for trimming the longest possible prefix or suffix, rather than the shortest.)

Solution 6 - Bash

I find that rename is a much more straightforward tool to use for this sort of thing. I found it on Homebrew for OSX

For your example I would do:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

The 's' means substitute. The form is s/searchPattern/replacement/ files_to_apply. You need to use regex for this which takes a little study but it's well worth the effort.

Solution 7 - Bash

better use sed for this, something like:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

figuring up the regular expression is left as an exercise (as is dealing with filenames that include spaces)

Solution 8 - Bash

I had multiple *.txt files to be renamed as .sql in same folder. below worked for me:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done

Solution 9 - Bash

This seems to work assuming that

  • everything ends with $pkg
  • your version #'s always start with a "-"

strip off the .pkg, then strip off -..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done

Solution 10 - Bash

Thank you for this answers. I also had some sort of problem. Moving .nzb.queued files to .nzb files. It had spaces and other cruft in the filenames and this solved my problem:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

It is based on the answer of Diomidis Spinellis.

The regex creates one group for the whole filename, and one group for the part before .nzb.queued and then creates a shell move command. With the strings quoted. This also avoids creating a loop in shell script because this is already done by sed.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJeremy LView Question on Stackoverflow
Solution 1 - BashrichqView Answer on Stackoverflow
Solution 2 - BashDiomidis SpinellisView Answer on Stackoverflow
Solution 3 - BashDiego SevillaView Answer on Stackoverflow
Solution 4 - BashocodoView Answer on Stackoverflow
Solution 5 - BashtripleeeView Answer on Stackoverflow
Solution 6 - BashSimon KatanView Answer on Stackoverflow
Solution 7 - BashJavierView Answer on Stackoverflow
Solution 8 - BashKumar MashalkarView Answer on Stackoverflow
Solution 9 - BashSteve B.View Answer on Stackoverflow
Solution 10 - BashJerry JacobsView Answer on Stackoverflow