234

Write a simple script that will automatically rename a number of files. As an example we want the file *001.jpg renamed to user defined string + 001.jpg (ex: MyVacation20110725_001.jpg) The usage for this script is to get the digital camera photos to have file names that make some sense.

I need to write a shell script for this. Can someone suggest how to begin?

Bart
  • 19,692
  • 7
  • 68
  • 77
station
  • 6,715
  • 14
  • 55
  • 89

8 Answers8

398

An example to help you get off the ground.

for f in *.jpg; do mv "$f" "$(echo "$f" | sed s/IMG/VACATION/)"; done

In this example, I am assuming that all your image files contain the string IMG and you want to replace IMG with VACATION.

The shell automatically evaluates *.jpg to all the matching files.

The second argument of mv (the new name of the file) is the output of the sed command that replaces IMG with VACATION.

If your filenames include whitespace pay careful attention to the "$f" notation. You need the double-quotes to preserve the whitespace.

Susam Pal
  • 32,765
  • 12
  • 81
  • 103
  • 10
    With bash, you can also redirect from a string: `sed 's/foo/bar/' <<< "$f"` – glenn jackman Jul 27 '11 at 11:08
  • 2
    Excellent example Susam--this usage also worked for me in MingW to change files named `*-1.0-SNAPSHOT.war` to `*.war`, like this: `for i in *.war; do mv ${i} \`echo ${i} | sed 's/-1.0-SNAPSHOT\.war/\.war/'\`; done` – mikequentel Jul 09 '15 at 16:03
  • just to add to this.. you can also use wild card, say for example there is a bunch of files with IMGnnnn.jpg where n is a bunch of nunbers; then you could do something like s/IMG.*/VACATION\\.jpg/ – Ahdee Aug 30 '17 at 15:54
  • 2
    @MikeStewart The GNU documentation you have linked to presents many non-POSIX features. See [POSIX.1-2008: § 2.6.2: Parameter Expansion](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02) for the POSIX specification on parameter expansion. The `${file//IMG/myVacation}` substitution in the other answer you have linked to does not conform to POSIX. It does not work with `dash`. On Debian, the default `sh` is `dash`. When `dash` encounters `${file//IMG/myVacation}`, it fails with `Bad substitution` error because this syntax does not conform to POSIX. – Susam Pal Sep 22 '20 at 09:38
  • @SusamPal Ah, yes, I was aware BASH has many features that are not POSIX compliant. (It says as much in the manual). But I'm glad you clarified and shared a more appropriate link for the example. Thanks! – Mike Stewart Sep 22 '20 at 22:43
  • It does not work inside a (GNU) Makefile! `mv: cannot stat '': No such file or directory` Why? – Christoph90 Oct 19 '20 at 14:36
  • @Christoph90 Are you escaping the `$` as `$$` within the `Makefile`? Perhaps it is worth asking a new question with your complete `Makefile`? – Susam Pal Oct 19 '20 at 16:31
  • @SusamPal That was exactly the issue, sorry for the confusion. – Christoph90 Oct 20 '20 at 10:08
207

You can use rename utility to rename multiple files by a pattern. For example following command will prepend string MyVacation2011_ to all the files with jpg extension.

rename 's/^/MyVacation2011_/g' *.jpg

or

rename <pattern> <replacement> <file-list>
KaiserKatze
  • 1,521
  • 2
  • 20
  • 30
ted
  • 3,911
  • 3
  • 26
  • 49
  • But there must be variants of this commands, rename is just like mv on my RedHat 6.5 – ixe013 Apr 03 '15 at 21:29
  • 1
    This didn't work for me, because I had too many files: `-bash: /usr/bin/rename: Argument list too long`. But I agree it's nice if you have a smaller list – TM. Nov 21 '15 at 17:50
  • 11
    @ixe013 This rename is perl version, not built-in utility. Follow the guide at http://stackoverflow.com/questions/22577767/get-the-perl-rename-utility-instead-of-the-built-in-rename to install. – 林果皞 Mar 23 '16 at 16:32
  • 2
    The "Argument list too long" problem can be circumvented by use of `find`: `find . -name *.jpg -exec rename {} \;` This will call rename once per file. – Erik Forsberg Nov 22 '16 at 12:26
  • I noted that in the first approach, if you keep out the `g` flag of the regular expression, it only applies the transform to the first matching entry -- good for testing. – JWL May 03 '18 at 05:53
  • I need to capture something from the original and place it somewhere else e.g. `something-123.jpg` to `123-something.jpg`? – CpILL Mar 22 '19 at 01:20
  • 1
    @CpILL it uses regex capture groups, and the replacement string will substitue `$` with the match. e.g. `rename 's/([^-]+)-([^.]+)/$2-$1/g' *`. The pattern `^([^-]+)-([^.]+)` means: from the start of the name, capture 1 or more chars that are NOT `-`, then expect a dash, then capture 1 or more chars that are not `.`. `$1` is the first capture, `$2` is the second. – Eric Haynes Apr 18 '19 at 15:53
  • Cool, thanks! Btw, I tried to use `rename ` today and my "replacement" started with a minus (`-4.6`), so the command complained with `rename: invalid option -- '4'`. Solved it by running `rename -- '' '' ` – RAM237 Nov 19 '19 at 12:44
73

this example, I am assuming that all your image files begin with "IMG" and you want to replace "IMG" with "VACATION"

solution : first identified all jpg files and then replace keyword

find . -name '*jpg' -exec bash -c 'echo mv $0 ${0/IMG/VACATION}' {} \; 
Sujit Dhamale
  • 1,311
  • 11
  • 14
  • 5
    Best answer as it allow traversal of the directory trees, uses built-in bash utilities and is easily configurable. Also, the example gives the non-destructive test to make sure the changes are correct. – Dan Mergens Jan 15 '18 at 21:16
  • 11
    Just a note here... pretty obvious but worth statting: the `'echo mv $0 ${0/IMG/VACATION}'` portion is the actual command that will be ran against each file found. So, if you leave the `echo` there, it will only echo what it would do. Remove the `echo` command to actually move the files eg. `find . -name '*jpg' -exec bash -c 'mv $0 ${0/IMG/VACATION}' {} \; ` – dmmd May 09 '18 at 23:55
  • 2
    Kudos for including `echo` so users know if it will work or not – Tung Mar 21 '19 at 08:00
  • most useful; though you should probably add the note from @dmmd. I actually dumped the output of the echo to a shell script for inspection then executed that... cuz I'm paranoid. – Brian Sep 29 '21 at 21:00
49
for file in *.jpg ; do mv $file ${file//IMG/myVacation} ; done

Again assuming that all your image files have the string "IMG" and you want to replace "IMG" with "myVacation".

With bash you can directly convert the string with parameter expansion.

Example: if the file is IMG_327.jpg, the mv command will be executed as if you do mv IMG_327.jpg myVacation_327.jpg. And this will be done for each file found in the directory matching *.jpg.

IMG_001.jpg -> myVacation_001.jpg
IMG_002.jpg -> myVacation_002.jpg
IMG_1023.jpg -> myVacation_1023.jpg
etcetera...

RJ Alten
  • 2,162
  • 1
  • 14
  • 7
  • 2
    @MikeStewart The GNU documentation you have linked to presents many non-POSIX features. See [POSIX.1-2008: § 2.6.2: Parameter Expansion](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02) for the POSIX specification on parameter expansion. The `${file//IMG/myVacation}` substitution in this answer does not conform to POSIX. It does not work with `dash`. On Debian, the default `sh` is `dash`. When `dash` encounters `${file//IMG/myVacation}`, it fails with `Bad substitution` error because this substitution syntax does not conform to POSIX. – Susam Pal Sep 22 '20 at 09:45
12
find . -type f | 
sed -n "s/\(.*\)factory\.py$/& \1service\.py/p" | 
xargs -p -n 2 mv

eg will rename all files in the cwd with names ending in "factory.py" to be replaced with names ending in "service.py"

explanation:

  1. In the sed cmd, the -n flag will suppress normal behavior of echoing input to output after the s/// command is applied, and the p option on s/// will force writing to output if a substitution is made. Since a sub will only be made on match, sed will only have output for files ending in "factory.py"

  2. In the s/// replacement string, we use "& " to interpolate the entire matching string, followed by a space character, into the replacement. Because of this, it's vital that our RE matches the entire filename. after the space char, we use "\1service.py" to interpolate the string we gulped before "factory.py", followed by "service.py", replacing it. So for more complex transformations youll have to change the args to s/// (with an re still matching the entire filename)

Example output:

foo_factory.py foo_service.py
bar_factory.py bar_service.py
  1. We use xargs with -n 2 to consume the output of sed 2 delimited strings at a time, passing these to mv (i also put the -p option in there so you can feel safe when running this). voila.

NOTE: If you are facing more complicated file and folder scenarios, this post explains find (and some alternatives) in greater detail.

Domi
  • 22,151
  • 15
  • 92
  • 122
John M Naglick
  • 1,791
  • 4
  • 14
  • 18
  • IMO this is the only valid solution. Beautiful, fast and flexible. – Konstantin Apr 07 '22 at 18:34
  • 1
    The best, most flexible solution. Especially in environments (like `cygwin`) where `perl replace` (the regex-enabled version of `replace` that is required in some of the above solutions) is not available or hard to come by. – Domi Jul 01 '22 at 06:11
4

Another option is:

for i in *001.jpg
do
  echo "mv $i yourstring${i#*001.jpg}"
done

remove echo after you have it right.

Parameter substitution with # will keep only the last part, so you can change its name.

Victor H
  • 69
  • 4
  • This does not allow for recursive file and folder structures and has several other shortcomings. [This solution](https://stackoverflow.com/a/40029320/2228771) by John supersedes this for all but the most trivial scenarios. – Domi Jul 01 '22 at 05:57
  • Little addendum to my previous comment: globs now support recursion with the [globstar option](https://stackoverflow.com/a/41639124). (But this solution is still missing regex replace support). – Domi Jul 01 '22 at 06:13
2

Can't comment on Susam Pal's answer but if you're dealing with spaces, I'd surround with quotes:

for f in *.jpg; do mv "$f" "`echo $f | sed s/\ /\-/g`"; done;
bentael
  • 1,987
  • 2
  • 21
  • 27
1

You can try this:

for file in *.jpg;
do
  mv $file $somestring_${file:((-7))}
done

You can see "parameter expansion" in man bash to understand the above better.

suvayu
  • 4,271
  • 2
  • 29
  • 35