4

I have a set of files with prefix, say "pre_", on a Linux machine and I just want to rename all of these files by removing that. Here is the perl code I wrote it doesn't throw any errors, but the work is not done.

#!/usr/bin/perl -w
my @files = `ls -1 | grep -i \"pre_.*\"`;
foreach $file ( @files )
{
  my @names = split(/pre_/, $file);
  my $var1 = $names[1];
  'mv "$file" "$var1"';
}
5
  • Debug. Print the old and new names just before the mv. My suspicion is that the grep pattern is wrong and returns no file names (maybe the backslashes are not wanted), but I don't do Perl. Or maybe the mv statement needs a keyword to actually execute it. Commented Aug 9, 2023 at 6:58
  • unix.stackexchange.com/questions/730894/… Commented Aug 9, 2023 at 7:41
  • 1
    Why are there straight quote marks around the mv line? Commented Aug 9, 2023 at 11:15
  • Are you willing to consider an answer written in Raku (formerly Perl6)? unix.stackexchange.com/a/676629/227738 Commented Aug 10, 2023 at 2:59
  • Do you really want to rename the files to the part in between the first and second occurrence of pre_ in their name? Why -i for grep, but no i flag in /pre_/. Do you or do you not want the match to be case insensitive? Commented Aug 10, 2023 at 5:58

3 Answers 3

13

You aren't executing anything. When you put something in single quotes (''), that's just a string, so 'mv "$file" "$var1"' is not a command, it's a string and doesn't do anything. A string by itself is just a true statement. In order to execute the command, you need to use:

system "mv", "--", $file, $var1;

(Not system("mv '$file' '$var1'") which would introduce a command injection vulnerability).

But there's no need for that, perl has a rename() function, so you can do:

rename($file, $var1)

Next, you really shouldn't parse the output of ls, that is a very bad idea and fragile. It is also completely unnecessary in Perl (or any other programming language). If you want to match all files in the current directory containing the string pre_ (which is what your grep does—you don't need the grep, by the way, you could have just used ls -d -- *pre_*), you can use glob() like this:

my @files = glob("*pre_*");

So, putting all that together, this is what you were trying to do:

#!/usr/bin/perl -w
use File::Glob qw(:globally :nocase);
my @files = glob("*pre_*");
foreach $file ( @files )
{
  my @names = split(/pre_/i, $file);
  rename($file, $names[1]);
}

But this isn't really a good idea. If you have two files with the same prefix (e.g. pre_file1 and apre_file1), then the first file will be overwritten by the second file since, in this example, both files would be renamed to file1 because your command would remove pre_ and everything before it. So you can add a warning and skip those files:

#!/usr/bin/perl -w
use File::Glob qw(:globally :nocase);

my @files = glob("*pre_*");
foreach $file ( @files )
{
  my @names = split(/pre_/i, $file);
  if (-e $names[1]) {
    warn "Warning: Not renaming '$file' to '$names[1]' " .
        "because '$names[1]' exists.\n";
    next;
  }
  rename($file, $names[1]) or
    warn "rename '$file': $!\n";
}

Of course, all of this is reinventing the wheel. You can just install perl-rename (on Debian, Ubuntu, etc. run apt install rename, on other systems it might be called prename or perl-rename), and then you can do (although this is not case insensitive, it only finds files containing pre_):

rename -n 's/.*?pre_//si' ./*

The -n causes rename to just print what it would do, without actually renaming. Once you are sure the renaming works as expected, run the command again without the -n to actually rename the files.

Here the shell passes all (non-hidden) files to rename and rename will ignore the files whose name the perl code doesn't change. You could also tell the shell to only pass the file names that contain pre_ case-insensitively with ./~(i)*pre_* in ksh93, ./(#i)*pre_* in zsh -o extendedglob, setting the nocaseglob option globally in bash, zsh or yash or use ./*[pP][rR][eE]_*.

5
  • 2
    Or perl -e 'for (<*>) {if (/pre_(.*)/is) {rename $_, $1 or warn "rename $_: $!\n"}}'. The OP seems to want to rename the file to the part after the first occurrence of pre_ case insensitively in the file name. Commented Aug 9, 2023 at 15:01
  • 1
    Or rename -n 's/.*?pre_//si' ./*pre_* (also note the ./ prefix without which you introduce a command injection vulnerability). Commented Aug 9, 2023 at 15:02
  • Very good point about the ./ @StéphaneChazelas, thanks, added. And thanks for pointing out the i, I missed that in the OP. I made the script case insensitive now.
    – terdon
    Commented Aug 9, 2023 at 16:06
  • 1
    +1. BTW, perl rename's features are also available to custom perl scripts via the File::Rename module - in fact, the perl rename tool is itself an example of a perl script using that module. I don't think the OP needs that here because they'd be better off just running rename from the shell.
    – cas
    Commented Aug 10, 2023 at 1:35
  • 1
    @Raffa, ah yes, good point, I overlooked that. See edit. Commented Aug 10, 2023 at 5:51
8

The tool you need already exists as rename (depending on your distro also perl-rename or prename)

rename 's/^pre_//' file1 file2 ...

Use -n for a dry run and -v for listing the renames.

Note: Not to be confused with the rename command from util-linux which works differently.

0
3

... Other renaming alternatives as well (rather than "reinventing the wheel") are ...

Using mmv like so:

$ mmv -n '*_*.ext' '#2.ext'
pre_file_number1.ext -> file_number1.ext
pre_file_number2.ext -> file_number2.ext
pre_file_number3.ext -> file_number3.ext
pre_file_number4.ext -> file_number4.ext

Or in the Z Shell(zsh) using its builtin function zmv like so:

% autoload zmv
% zmv -n '*.ext' '${f#*_}' 
mv -- pre_file_number1.ext file_number1.ext
mv -- pre_file_number2.ext file_number2.ext
mv -- pre_file_number3.ext file_number3.ext
mv -- pre_file_number4.ext file_number4.ext

Notice: -n in both cases above is for a safe dry-run ... When satisfied with the output, remove it or substitute it with -v(for verbosity) so the actual renaming will happen.

You must log in to answer this question.

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