Linux: rename file but keep extension?

10

1

In Windows/DOS, I can say rename myfile.* yourfile.* to change the name but keep the extension. How is that accomplished on Linux?

The man page only suggests how to change the extension, but that's the opposite of what I want.

Bonus:
I actually want to put a photo's creation date into its filename, to get something like 20091231 2359 New Year.jpg. I'm afraid that I need some non-trivial combination of commands to achieve that?

Torben Gundtofte-Bruun

Posted 2010-01-01T10:08:28.430

Reputation: 16 308

Answers

14

Here's an answer for the bonus question.

I actually want to put a photo's creation date into its filename, to get something like 20091231 2359 New Year.jpg. I'm afraid that I need some non-trivial combination of commands to achieve that?

Assuming you want to take the photo's creation date from the EXIF data, you'll need a separate tool for that. Luckily it turns out that jhead offers a trivial way to do exactly what you want, with its -n option.

$ jhead -h
 [...]

 -n[format-string]

             Rename files according to date.  Uses exif date if present, file
             date otherwise.  If the optional format-string is not supplied,
             the format is mmdd-hhmmss.  If a format-string is given, it is
             is passed to the 'strftime' function for formatting
             In addition to strftime format codes:
             '%f' as part of the string will include the original file name
             [...]

Here's an example:

$ jhead -n%Y-%m-%d-%f New_year.jpg   
New_year.jpg --> 2009-12-31-New_year.jpg

Edit: Of course, to do this for a bunch of photos, it'd be something like:

$ for i in *jpg; do jhead -n%Y-%m-%d-%f $i; done

To tweak the date formatting to your liking, take a look at the output of date --help, for example; it will list the available format codes.

(jhead is widely available for different systems. If you are e.g. on Ubuntu or Debian, simply type sudo apt-get install jhead to install it.)

Jonik

Posted 2010-01-01T10:08:28.430

Reputation: 5 352

1I had not thought of jhead, only of an ugly combination of rename, stat, and cut. Thank you for the great answer! – Torben Gundtofte-Bruun – 2010-01-01T13:56:40.683

10

For just the renaming part, the 'rename' program will work. It's the same as the example you saw in the man page, just switched around.

justin@eee:/tmp/q$ touch myfile.{a,b,c,d}
justin@eee:/tmp/q$ ls
myfile.a  myfile.b  myfile.c  myfile.d
justin@eee:/tmp/q$ rename -v s/myfile/yourfile/ myfile.*
myfile.a renamed as yourfile.a
myfile.b renamed as yourfile.b
myfile.c renamed as yourfile.c
myfile.d renamed as yourfile.d
justin@eee:/tmp/q$ ls
yourfile.a  yourfile.b  yourfile.c  yourfile.d
justin@eee:/tmp/q$ 

user23307

Posted 2010-01-01T10:08:28.430

Reputation: 5 915

This is really useful! I was surprised by how many of these types of questions I had to search through before finding "rename". Thank you. – alanning – 2016-02-09T17:09:51.153

1On some distros, this perl rename program is called prename. – camh – 2010-01-04T09:56:44.563

7

betelgeuse:tmp james$ ls myfile.* yourfile.*
ls: yourfile.*: No such file or directory   
myfile.a    myfile.b
betelgeuse:tmp james$ for file
> in myfile.*
> do
> mv "${file}" "`echo $file | sed 's/myfile\./yourfile./'`"
> done
betelgeuse:tmp james$ ls myfile.* yourfile.*
ls: myfile.*: No such file or directory
yourfile.a  yourfile.b

The key is that, if you've seen an example which shows how to munge one part of the filename with a regex, that's the only example you need. Extensions have no special status on unix filesystems - they're just a part of the filename that happens to be after a . character.

James Polley

Posted 2010-01-01T10:08:28.430

Reputation: 5 892

1This is the answer to my question. (But the other answer gets me there faster&easier.) – Torben Gundtofte-Bruun – 2010-01-01T13:55:46.740

4No need to fork+exec sed: mv "$file" yourfile"${file#myfile}". Works on any modern Bourne-like shell (or any POSIX shell), but probably not the actual Bourne shell. – Chris Johnsen – 2010-01-01T18:03:13.697

4

Here are a couple more different ways to manipulate filenames

for f in *.jpg
do
    mv "$f" "before_part${f%.*}after_part.${f##*.}"
    # OR mv "$f" "before_part$(basename "$f" ".jpg")after_part.jpg"
done

The parameter expansions in the mv command work as follows:

${f%.*} - Delete the shortest matching pattern from the end of the string contained in $f, in this case delete everything after and including the last dot. The single % means "shortest from the end".

${f##*.} - Delete the longest matching pattern from the beginning of the string contained in $f, in this case everything before and including the last dot (this includes any other dots as well). The double # (##) means "longest from the beginning".

So, for example, if $f contains "Foo.bar.baZ.jpg":

echo "${f%.*}"

gives

Foo.bar.baZ

and

echo "${f##*.}"

gives

jpg

So the mv command, once expanded would look like:

mv "Foo.bar.baZ.jpg" "before_partFoo.bar.baZafter_part.jpg"

Paused until further notice.

Posted 2010-01-01T10:08:28.430

Reputation: 86 075

I actually ended up using the FOR loop but not the MV example because I don't understand it :-) – Torben Gundtofte-Bruun – 2010-01-26T22:00:52.957

Note: The meaning of those f% and f## are described here, for instance: http://www.tldp.org/LDP/abs/html/parameter-substitution.html

– Torben Gundtofte-Bruun – 2016-02-10T09:02:35.890

3

There are no filename extensions in Linux.

Use regular expressions to cut particular substrings from the filename and access them.

Example:

Real-life scenario: you are extracting html from a chm file. Filenames in Windows are case-insensitive, so in Linux you'll get broken links. You have a file named index.HTML, but href="index.html" in URLs. So your goal is to adapt filenames to match links to them.

Assume you have the filename in a variable:

FILENAME='index.HTML'

Starting with version 3.0 bash supports regular expressions itself, so you don't need any additional tools like grep/sed/perl etc to perform string manipulation. The following example illustrates the replacement of a back-end match in a string:

echo ${FILENAME/%\.HTML/.html}

The match and replacement strings can be parametrized if you wish, this provides additional flexibility when writing script. The following code snippet achieves the same goal:

match='\.HTML'
replacement='.html'
echo ${FILENAME/%$match/$replacement}

Consult the bash docs for additional info.

geek

Posted 2010-01-01T10:08:28.430

Reputation: 7 120

1Any example, on these regular expressions? – Gnoupi – 2010-01-01T10:21:32.273

+1. This answer is very useful now (after the edit) – I had no idea you can do such string manipulation in Bash. – Jonik – 2010-01-02T17:04:21.527

Indeed, much better now, with examples. +1 – Gnoupi – 2010-01-06T08:25:18.647

0

You can use the -X/--keep-extension option with rename (version 1.600):

-X, --keep-extension Save and remove the last extension from a filename, if there is any. The saved extension will be appended back to the filename at the end of the rest of the operations.

rename is available for Mac from Homebrew and for Linux from Linuxbrew (among other install options of course).

John

Posted 2010-01-01T10:08:28.430

Reputation: 101

0

Here's another:

find -name "*.jpg" -printf '"%p" "%h/%TY%Tm%Td %TH%TM %f"\n' | while read -r f
do
    eval "mv ${f}"
done

Paused until further notice.

Posted 2010-01-01T10:08:28.430

Reputation: 86 075

0

There's always more than one way to do it. I put the following script as /usr/local/bin/mrename.

Then in the script containing the photo files, just type: mrename

There's also an optional commented-out feature in the script to scale the photos (using ImageMagick).

Hope this is useful for some folks.

#!/usr/bin/perl
#
# mrename files
#
#
use strict;

# if no 2 args, use defaults
my $dir = ".";

# read in path from command line
$dir = $ARGV[0] if ( defined( $ARGV[0] ) && $ARGV[0] ne "" );

# read in directory contents
opendir( DIR, $dir );
my @files = readdir( DIR );
closedir( DIR );

# rename and/or scale each file in directory
my $number_of_files = scalar( @files );
my $curfile = 0;

foreach my $file( @files ) {
    # only rename and scale jpg/gif files
    if ( $file =~ /\w+\.(jpg)$/ ) {
        my $extension = $1;
        $extension =~ tr/A-Z/a-z/;
        my $full_filename = "$dir/$file";

        # get stats on file- specifically the last modified time
        (my $dev,my $ino,my $mode,my $nlink,my $uid,my $gid,my $rdev,my $size,
        my $atime,my $mtime,my $ctime,my $blksize,my $blocks) = stat($full_filename);

        # convert last-modified time from seconds to practical datetime terms
        (my $sec,my $min,my $hour,my $mday,my $mon,my $year,my $wday,my $yday,
        my $isdst) = localtime($mtime);

        ++$mon;
        $year += 1900;

        my $filecdate = sprintf( "m%04i%02i%02i_%02i%02i%02i.$extension", $year, $mon, $mday, $hour, $min, $sec );
        my $full_newfilename = "$dir/$filecdate";

        # to scale files, use imagemagick by using the command below instead of mv 
        #my $cmd = "convert $full_filename -resize $scale% $full_newfilename";
        my $cmd = "mv $full_filename $full_newfilename";
        system( $cmd );

        # update percentage done
        my $percent_done = sprintf( "%5.2lf", 100* (++$curfile) / $number_of_files );
        print "\r$percent_done%";
    }
}
print "\n";

user23313

Posted 2010-01-01T10:08:28.430

Reputation: