bash - String replacement in filenames in linux
2014-04
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?
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
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/'
renamer works on Windows, Mac and Linux:
$ renamer --find Find30 --replace Find20 *.txt
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?
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.)
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.
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
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$
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.
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
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";