Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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?

share|improve this question
up vote 12 down vote accepted

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.)

share|improve this answer
1  
I had not thought of jhead, only of an ugly combination of rename, stat, and cut. Thank you for the great answer! – Torben Gundtofte-Bruun Jan 1 '10 at 13:56

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$ 
share|improve this answer
1  
On some distros, this perl rename program is called prename. – camh Jan 4 '10 at 9:56
    
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 Feb 9 at 17:09
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.

share|improve this answer
1  
This is the answer to my question. (But the other answer gets me there faster&easier.) – Torben Gundtofte-Bruun Jan 1 '10 at 13:55
4  
No 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 Jan 1 '10 at 18:03

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"
share|improve this answer
    
I actually ended up using the FOR loop but not the MV example because I don't understand it :-) – Torben Gundtofte-Bruun Jan 26 '10 at 22:00
    
Note: The meaning of those f% and f## are described here, for instance: tldp.org/LDP/abs/html/parameter-substitution.html – Torben Gundtofte-Bruun Feb 10 at 9:02

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.

share|improve this answer
1  
Any example, on these regular expressions? – Gnoupi Jan 1 '10 at 10:21
    
+1. This answer is very useful now (after the edit) – I had no idea you can do such string manipulation in Bash. – Jonik Jan 2 '10 at 17:04
    
Indeed, much better now, with examples. +1 – Gnoupi Jan 6 '10 at 8:25

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
share|improve this answer

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";
share|improve this answer

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .