Perl script to make first 8 characters of all headers, but not all filename
Which Perl script should I use to change only the first 8 characters in the filename for all caps, instead of a script to change the entire filename for all caps?
This is how I set it up:
#!/usr/bin/perl
chdir "directory path";
#@files = `ls *mw`;
@files = `ls | grep mw`;
chomp @files;
foreach $oldname (@files) {
$newname = $oldname;
$newname =~ s/mw//;
print "$oldname -> $newname\n";
rename("$oldname","$newname");
}
source to share
Substitution
s/^(.{1,8})/\U$1/
will set the first eight characters of the string to uppercase. The complete program looks like this:
use strict;
use warnings;
chdir "directory path" or die "Unable to change current directory: $!";
opendir my $dh, '.' or die $!;
my @files = grep -f && /mw/, readdir $dh;
foreach my $file (@files) {
(my $new = $file) =~ s/mw//;
$new =~ s/^(.{1,8})/\U$1/s;
print "$file -> $new\n";
rename $file, $new;
}
source to share
What about:
#!/usr/bin/perl
use strict;
use warnings;
use File::Copy;
chdir'/path/to/directory';
# Find all files that contain 'mw'
my @files = glob("*mw*");
foreach my $file(@files) {
# skip directories
next if -d $file;
# remve 'mw' from the filename
(my $FILE = $file) =~ s/mw//;
# Change filename to uppercase even if the length is <= 8 char
$FILE =~ s/^(.{1,8})/uc $1/se;
move($file, $FILE);
}
As the doc says for rename , you are better off using File :: Copy to be platform independent.
source to share
Always check the return values of system calls!
When accessing OS services, you should always check the return value. For example, the Perl documentation for chdir
(with emphasis)
chdir EXPR
chdir FILEHANDLE
chdir DIRHANDLE
chdir
Change the working directory to EXPR if necessary. If EXPR is omitted, changes to the directory specified
$ENV{HOME}
, if installed; if not, changes to the directory specified$ENV{LOGDIR}
. (In VMS, the variable is$ENV{SYS$LOGIN}
also checked and used if set.) If none of these are set,chdir
does nothing. It returns true on success, false otherwise. See example under die.On systems that support fchdir (2), you can pass a file descriptor or directory descriptor as an argument. On systems that do not support fchdir (2), passing descriptors throw an exception.
As stated in your question, your code is throwing away important information: whether the system calls were chdir
both rename
successful or unsuccessful.
Providing helpful error messages
An example of a common idiom for checking return values in Perl is
chdir $path or die "$0: chdir $path: $!";
The error message contains three important bits of information:
Also note that die
also the filename and line number where programmatic control was set, unless your error message ends with a newline character. When chdir
not working, the standard error will be like
./myprogram: chdir: No such file or directory at ./myprogram line 3.
Logically or true when at least one of its arguments is true. The do-or-die idiom works because if the chdir
above fails, it returns false and requires or
the right-hand side to evaluate and exits with die
. In the lucky case where chdir
succeeds and returns true, there is no need to evaluate the right-hand side because we already have one true argument for the boolean or.
Recommended code improvements
For what you are doing, I recommend using readdir
to avoid problems if one of the filenames contains spaces. Note defined
in the code below to stop the file named 0
(i.e. One null character) ending your loop.
#! /usr/bin/env perl
chdir "directory path" or die "$0: chdir: $!";
opendir $dh, "." or die "$0: opendir: $!";
while (defined($oldname = readdir $dh)) {
next unless ($newname = $oldname) =~ s/mw//;
$newname =~ s/^(.{1,8})/\U$1/;
rename $oldname, $newname or die "$0: rename $oldname, $newname: $!";
}
In rename
order to have any hope, you must store the value $oldname
, so immediately the code above copies it to $newname
and starts replacing the copy, not the original. You will see
($new = $old) =~ s/.../.../; # or /.../
in Perl code, so this is also an important idiom to understand.
The perlop documentation defines convenient escape sequences for use in strings and regular expression substitutions:
\l
lowercase next character\u
titlecase only (not uppercase!) next character
\l
lowercase only all characters before \ E seen
\u
uppercase all characters before \ E seen
\Q
quote non-word characters before \ E
\E
complete either case modification or quote (in depending on what happened last)
The above code grabs the first eight characters (or less if $newname
shorter in length) and replaces them with their upcased counterparts.
Output example
See the code in action:
$ ls directory \ path / defmwghijk mwabc nochange qrstuvwxyzmw $ ./prog $ ls directory \ path / ABC DEFGHIJK QRSTUVWXyz nochange
source to share
I rely more on your requirements than you tell us, for example not on the upper end of the parts of the file extension. Instead of matching the first eight characters, I'll match the first eight letters:
use v5.14;
use utf8;
chdir "/Users/brian/test/";
my @files = glob( 'mw*' );
foreach my $old (@files) {
my $new = $old =~ s/\Amw(\pL{1,8})/\U$1/ir;
print "$old → $new\n";
}
Some other notes:
- You can glob directly in Perl. You don't need to
ls
. - It looks like you were disconnecting
mv
, which is why I did it. If that's not what you want, it's easy to change.
source to share
Instead of a regular expression to repeat the first eight characters, you can use the 4-argument form substr . This offers an on-site replacement.
my $old = q(abcdefghij);
my $new = $old;
substr( $new, 0, 8, substr( uc($old), 0, 8 ) );
print "$old\n$new\n";
abcdefghij
ABCDEFGHij
Use rename
or File::Copy::move
(as shown in M42) to perform the actual rename.
source to share