Skip to content Skip to sidebar Skip to footer

Batch Rename Directories In Reverse Order

I have a bunch (more than 700) directories that are named in the following manner 1 - *random*name*1* 2 - *random*name*2* ... 725 - *random*name*725* The leading numbers (1-725) a

Solution 1:

One way using perl. Code is commented.

Content of script.pl:

#!/usr/bin/env perl

use warnings;
use strict;
use File::Basename qw|basename|;

die qq{Usage: perl $0 <real|trial>\n} unless @ARGV == 1;

my $real = $ARGV[0] eq q|real| ? 1 : 0;

## Filter filenames from the directory that match criteria of OP.
my @files = grep { -d $_ && m/\A(\d+)\s-\s.*\1/ } map { basename $_ } <./*>;

## Get numbers in reverse mode to substitute them later in filenames .
my @nums = map { m/\A(\d+)/; $1 || 0 } reverse @files;

## Process all files, change the number of the file and rename it.
for my $file ( @files ) { 
    (my $new_file = $file) =~ s/\A(\d+)/shift @nums/e;

    if ( $real ) { 
        rename $file, $new_file or warn qq|WARN: Couldn't rename "$file"\n|;
    }   
    else {
        printf qq|%s\n|, qq|rename "$file" "$new_file"|;
    }   
}

List directory:

ls -1

With following output:

1 - file1file
2 - fi2le
3 - fileeee3
4 - fou4r
5 -  donttouch
6 -  donttouch5either
script.pl

Run the script:

perl script.pl

list the directoy again:

ls -1

With following output:

1 - fou4r
2 - fileeee3
3 - fi2le
4 - file1file
5 -  donttouch
6 -  donttouch5either
script.pl

EDIT: There was a suggestion in comments to let a trial run before the real one, so I added an argument real or trial. With first option it will do real changes, while with second one it will print to output the real command but without running them. Now run the script like:

perl script.pl real

or

perl script.pl trial

Solution 2:

Let's first simulate some filenames:

$ printf 'kjdfh8\nskdjfh9\nckjhv10\naskjdh11\n'
kjdfh8
skdjfh9
ckjhv10
askjdh11

Then run 'em through a pipe:

$ printf 'kjdfh8\nskdjfh9\nckjhv10\naskjdh11\n' | sed 's/[^0-9]*\(.*\)/\1 &/' | sort -nr | cut -d\  -f2-
askjdh11
ckjhv10
skdjfh9
kjdfh8

How does this work? It's actually really simple. First, we use sed to put a copy of any numeric content at the beginning of the line. Next, we sort. Finally, we use cut to strip off the number at the beginning of the line. And voilĂ !

Now... That just reverses the order. That's one of the questions in your question, but your end goal is to rename things. We can extend the list above with another pipe that is also a while loop. This time I'll space things out for easier reading.

#!/bin/bash

n=0
printf 'kjdfh8\nskdjfh9\nckjhv10\naskjdh11\n' \
| sed 's/[^0-9]*\(.*\)/\1 &/' | sort -nr | cut -d\  -f2- \
| while read file; do
    base="${file/[0-9]*/}"
    ((n++))
    printf 'mv "%s" "%s%s"\n' "$file" "$base" "$n"
  done

This while loop reads in the list of files we created above. It gets the "base" by removing everything in the filename starting with the first digit. It increments a counter (used for the new name), then prints a mv command with the old filename, base and counter.

Again, the output of this script is a set of mv commands which you can pipe through a shell.

Obviously, to use this on your files, you will need to replace the initial printf command with something that generates a list of your files.

NOTE: It is usually inadvisable to process filenames in this way, due to the risk of unexpected or special characters (even spaces) turning up in filenames and throwing off your plans. Only do this if you're confident that you understand it.


Solution 3:

if this awk solution ok for you?

cmd:

ls -F|grep "/$"|sort|awk 'BEGIN{FS=" - ";l=5;}{o=$0;gsub("/$","",o);gsub($1"/$",l--);if(o!=$0)printf "mv \"%s\" \"%s\"\n",o,$0 }' |sh

here the 5 is hard coded, in your case would be 725. :) of course it could be set by wc -l, but I am abit lazy.

test:

kent$  tree -d
.
|-- 1 - foo1
|-- 2 - foo2
|-- 3 - foo3
|-- 4 - foo4
`-- 5 - foo5

5 directories

#this will only print the generated mv command:

kent$  ls -F|grep "/$"|sort|awk 'BEGIN{FS=" - ";l=5;}{o=$0;gsub("/$","",o);gsub($1"/$",l--);if(o!=$0)printf "mv \"%s\" \"%s\"\n",o,$0 }'   
mv "1 - foo1" "1 - foo5"
mv "2 - foo2" "2 - foo4"
mv "4 - foo4" "4 - foo2"
mv "5 - foo5" "5 - foo1"

# if you pipe the generated command to "sh", they will be executed:

kent$  ls -F|grep "/$"|sort|awk 'BEGIN{FS=" - ";l=5;}{o=$0;gsub("/$","",o);gsub($1"/$",l--);if(o!=$0)printf "mv \"%s\" \"%s\"\n",o,$0 }' |sh


#result:

kent$  tree -d
.
|-- 1 - foo5
|-- 2 - foo4
|-- 3 - foo3
|-- 4 - foo2
`-- 5 - foo1

5 directories

Solution 4:

This:

find /path/to/the/directories/location/ -depth -exec mv '{}' $(basename '{}')$(echo $(date "+%H%M%S%N")) \;

Post a Comment for "Batch Rename Directories In Reverse Order"