5

I'm looking to bulk rename files in the current directory only and remove certain strings from the end of file names.

Sample:

foo-bar-(ab-4529111094).txt
foo-bar-foo-bar-(ab-189534).txt
foo-bar-foo-bar-bar-(ab-24937932201).txt

the output should look like this:

foo-bar.txt
foo-bar-foo-bar.txt
foo-bar-foo-bar-bar.txt

I want to remove the string -(ab-2492201) at the end of each file name knowing that the digits can vary in length.

A Perl regex is preferred over modules and without using any utilities and for bash oneliner command is highly preferred.

How to accomplish that in both Perl and Bash Shell on Linux? interested to know both solutions.

Borodin
  • 126,100
  • 9
  • 70
  • 144
SilverShadow
  • 185
  • 2
  • 13
  • Why do you not want to use any Perl modules? – Borodin Jan 09 '13 at 22:40
  • to make it portable on multiple machines after inserting the code inside my main script, so not every machine will have to install modules dependencies. – SilverShadow Jan 09 '13 at 23:06
  • There are very many perl "core" modules that should be available with any standard Perl installation. Once of them is `File::Find`, but it seems that isn't required for your problem. – Borodin Jan 09 '13 at 23:18

7 Answers7

7

Try:

$ rename 's/-\(ab-\d+\)(?=\.txt$)//' *.txt

There's a rename command written in Perl. Its first argument is Perl code describing how to transform a filename. You could use the same s/// command in your own Perl program or one-liner.

If that doesn't work, try prename instead of rename; there's a different, non-Perl-based, rename command installed on some systems, in which case the Perl one may be called prename.

Smylers
  • 1,673
  • 14
  • 18
  • Odd. I just created files with the exact names as in your question then pasted in the above `rename` command and it worked fine. What happened when you tried it? Was there any error message? If you put the `-v` option in the `rename` command, does that give any output? – Smylers Jan 10 '13 at 00:11
  • 2
    There is apparently more than one command called "rename"; some Linux distributions come with a non-perl version that works differently. See: http://stackoverflow.com/questions/22577767/get-the-perl-rename-utility-instead-of-the-built-in-rename – Arnon Weinberg Jan 13 '15 at 03:22
  • Arnon Weinberg: Good point, thanks. Answer updated to reflect that. – Smylers Jan 13 '15 at 09:10
5

Using Perl Regex to Rename Files

With find, perl, and xargs, you could use this one-liner

find . -type f | perl -pe 'print $_; s/input/output/' | xargs -n2 mv

Results without calling mv should just be

OldName NewName
OldName NewName
OldName NewName

How does it work?

  1. find . -type f outputs file paths (or file names...you control what gets processed by regex here!)
  2. -p prints file paths to be processed by regex, -e executes inline script
  3. print $_ prints the original file name first (independent of -p)
  4. -n2 prints two elements per line
  5. mv gets the input of the previous line
Jonathan Komar
  • 2,678
  • 4
  • 32
  • 43
4

In bash, you could write something like:

for file in *-\(ab-[0-9]*\)*; do
    newfile="${file/-(ab-[0-9]*)/}"
    mv "$file" "$newfile"
done
larsks
  • 277,717
  • 41
  • 399
  • 399
2

When you say under the current directory, do you mean in the current directory, or anywhere in or beaneath the current directory and its descendants?

File::Find is a simple way to do the latter, and is a core module so won't need installing. Like so:

use strict;
use warnings;

use autodie;

use File::Find;

find(\&rename, '.');

sub rename {
  return unless -f;
  my $newname = $_;
  return unless $newname =~ s/-\(ab-[0-9]+\)(\.txt)$/$1/i;
  print "rename $_, $newname\n";
}

Update

This program will rename all the files with the given filename pattern only within the current directory.

Note that the initial open loop is there only to create sample files for renaming.

use strict;
use warnings;

use autodie;

open my $fh, '>', $_ for qw(
  foo-bar-(ab-4529111094).txt
  foo-bar-foo-bar-(ab-189534).txt
  foo-bar-foo-bar-bar-(ab-24937932201).txt
);

for (glob '*.txt') {
  next unless -f;
  my $newname = $_;
  next unless $newname =~ s/-\(ab-[0-9]+\)(\.txt)$/$1/i;
  print "rename $_, $newname\n";
  rename $_, $newname;
}

output

rename foo-bar-(ab-4529111094).txt, foo-bar.txt
rename foo-bar-foo-bar-(ab-189534).txt, foo-bar-foo-bar.txt
rename foo-bar-foo-bar-bar-(ab-24937932201).txt, foo-bar-foo-bar-bar.txt
Borodin
  • 126,100
  • 9
  • 70
  • 144
1

A simpler, shorter (better ? :) ) rename regex :

rename 's@-\(.*?\)@@' foo*.txt
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
  • didn't work, sorry, please note that the string "foo" at the start of file names could be anything like multiple words separated by a dash - – SilverShadow Jan 09 '13 at 23:08
  • What @sputnick wrote will work with anything at the start of the filename, so long as it then has a hyphen, an opening paren, and (some point later) a closing paren. Did you try creating the exact filenames you mention in your question and see if it works on those? – Smylers Jan 10 '13 at 06:38
1

check this:

ls -1 | nawk '/foo-bar-/{old=$0;gsub(/-\(.*\)/,"",$0);system("mv \""old"\" "$0)}'

> ls -1 foo*
foo-bar-(ab-4529111094).txt
foo-bar-foo-bar-(ab-189534).txt
foo-bar-foo-bar-bar-(ab-24937932201).txt

> ls -1 | nawk '/foo-bar-/{old=$0;gsub(/-\(.*\)/,"",$0);system("mv \""old"\" "$0)}'

> ls -1 foo*
foo-bar-foo-bar-bar.txt
foo-bar-foo-bar.txt
foo-bar.txt
> 

For detailed explanation check here

Vijay
  • 65,327
  • 90
  • 227
  • 319
0

Another way using just perl:

perl -E'for (<*.*>){ ($new = $_) =~ s/(^.+?)(-\(.+)(\..*$)/$1$3/; say  $_." -> ".$new}'

(say ... is nice for testing, just replace it with rename $_,$new or rename($_,$new) )

  1. <*.*> read every file in the current directory
  2. ($new = $_) =~ saves the following substitution in $new and leaves $_ as intact
  3. (^.+?) save this match in $1 and non-greedy match from the beginning until...
  4. (-\(.+) the sequence "-( ...anything..." is found. (this match would be saved in $2)
  5. (\..*$) save everything from the last "." (period) before the end ($) of the line until and including the end of the line -> into $3
  6. substitute the match with the string generated from $1$3

( you could also do it for a specific directory with perl -E'for (</tmp/my/directory/*.*>){ .....

MacMartin
  • 2,366
  • 1
  • 24
  • 27