bash - String replacement in filenames in linux

06
2014-04
  • Jack Medley

    I have a tonne of files called File30_i.txt for i=1, 2, 3, 4, 5... Is there an easy way to quickly rename then File20_i.txt for example?

  • Answers
  • lesmana

    There is a tool for that called rename:

    rename 's/File30/File20/' *.txt
    

    For a preview run with -n:

    rename -n 's/File30/File20/' *.txt
    
  • tr00st

    My preferred way would be to use sed to generate a script for it, for example:

    ls -1|sed 's/File30_\([^.]*\).txt/mv & File20_\1.txt/'|bash
    

    This should rename as per the filenames you gave in the question. To preview the commands, you can do:

    ls -1|sed 's/File30_\([^.]*\).txt/mv & File20_\1.txt/'
    
  • Lloyd

    renamer works on Windows, Mac and Linux:

    $ renamer --find Find30 --replace Find20 *.txt
    

  • Related Question

    bash - Linux: rename file but keep extension?
  • Torben Gundtofte-Bruun

    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?


  • Related Answers
  • Jonik

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

  • James Polley
    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.

  • Dennis Williamson

    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
    
  • user23307

    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$ 
    
  • geek

    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.

  • Dennis Williamson

    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
    
  • Area 51

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