15

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?

10 Answers 10

15

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

1
  • 2
    I had not thought of jhead, only of an ugly combination of rename, stat, and cut. Thank you for the great answer! Jan 1, 2010 at 13:56
11

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$ 
2
  • 1
    On some distros, this perl rename program is called prename.
    – camh
    Jan 4, 2010 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, 2016 at 17:09
9

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"
2
8
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.

2
  • 1
    This is the answer to my question. (But the other answer gets me there faster&easier.) Jan 1, 2010 at 13:55
  • 5
    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. Jan 1, 2010 at 18:03
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.

3
  • 1
    Any example, on these regular expressions?
    – Gnoupi
    Jan 1, 2010 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, 2010 at 17:04
  • Indeed, much better now, with examples. +1
    – Gnoupi
    Jan 6, 2010 at 8:25
1

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

0

I did not understand how to use the rename example. However I wrote my own bash script, which takes the original file name with its extension and the new name without extension:

# trans_file_extension:
# transfers the file extension from path_file to new_path_no_extension
# and returns both
#!/bin/bash 
path_file=$1
new_path_no_extension=$2

dir_name=$(dirname $path_file)
base_name=$(basename $path_file)

name_without_extension="${base_name%%.*}"
extension="${base_name#*.}"

path_copy=$new_path_no_extension"."$extension

echo $path_file  $path_copy 

You can then use it with move or copy via:

mv `./trans_file_extension tmp.json tmp_copy`
cp `./trans_file_extension tmp.json tmp_copy`
0

If the other answer using the rename command doesn't work for you, try this:

touch myfile.txt
rename -v myfile yourfile myfile.*

The -v flag isn't necessary. It just turns on the verbose option which tells you what actions it is taking.

Rename versions

The other rename answer assumes you're using the version from the perl package. That version is only available on certain distros. The answer here assumes you're using the rename command from the util-linux package. This command is available on almost all distros (though the name may be different).

See this answer for more details: https://unix.stackexchange.com/questions/730894/what-are-the-different-versions-of-the-rename-command-how-do-i-use-the-perl-ver

You must log in to answer this question.

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