1

I need to rename a lot of directories and files in my Linux/Solaris machines. There are many links that pointed to those directories/files.

So first I create the following script in order to find all directories that I want to rename (like DOMAIN.city.country, DOMAIN.type.country etc.) and their links.

The script takes the old and new names of the node from file info.file file. The first field in each line represents the old directory name and the second field represents the new directory name.

File find_and_recreate_link_to_new_dir.ksh

while read -r line ; do 

[[ -z $line ]] && continue

name_old_dir=` echo $line | awk '{print $1}' `
name_new_dir=` echo $line | awk '{print $2}' `

find / -type l -exec ls -l 2>/dev/null '{}' \; |  perl -ne'BEGIN { $str = shift(@ARGV); }     print if /\Q$str\E/; ' $name_old_dir

done  < /tmp/test/info.file

File /tmp/test/info.file

    DOMAIN.city.country DOMAIN.world.country
    DOMAIN.city1.country DOMAIN.world1.country
    ...

When I run my script I get this output

lrwxrwxrwx 1 root root 19 Mar 15 11:22 /var/test/info.domain2.com -> DOMAIN.city.country
lrwxrwxrwx 1 root root 19 Mar 15 11:22 /var/test/info.domain.com -> DOMAIN.city.country
lrwxrwxrwx 1 root root 19 Mar 15 11:22 /var/test/info.domain1.com -> DOMAIN.city.country
lrwxrwxrwx 1 root root 5 Mar 15 11:57 /var/test/DOMAIN.type.country -> mkdir
lrwxrwxrwx 1 root root 19 Mar 15 11:58 /var/test/info.tyep.com -> DOMAIN.type.country
...

Now I need to add to this script a section that will recreate the current links to the new directories. For example

/var/test/info.domain2.com -> DOMAIN.world.country

Please advise the best solution to automatically recreate the current links to the new directories.

Borodin
  • 126,100
  • 9
  • 70
  • 144

2 Answers2

1

If I understand the problem properly, you want to create symbolic links based on the information coming from find_and_recreate_link_to_new_dir.ksh.

So if you get

lrwxrwxrwx 1 root root 19 Mar 15 11:22 /var/test/info.domain2.com -> DOMAIN.city.country

you will want to create the link

/var/test/info.domain2.com -> DOMAIN.city.country

In this case, what I recommend you is to store both parameters and call ln -s with them. It can be interesting to save data in a file, let's say FILE_WITH_NEW_DATA:

find / -type l -exec ls -l 2>/dev/null '{}' \; |  perl -ne'BEGIN { $str = shift(@ARGV); }     print if /\Q$str\E/; ' $name_old_dir >> FILE_WITH_NEW_DATA

Then, given the string

lrwxrwxrwx 1 root root 19 Mar 15 11:22 /var/test/info.domain2.com -> DOMAIN.city.country

you can get the parameters with:

link = $(awk 'N=NF-2 {print $N}')
file = $(awk '{print $NF}')

and then create the symbolic link with ln -s $file $link.

All together,

... 

find / -type l -exec ls -l 2>/dev/null '{}' \; |  perl -ne'BEGIN { $str = shift(@ARGV); }     print if /\Q$str\E/; ' $name_old_dir >> FILE_WITH_DATA

...


while read -r line ; do 

  [[ -z $line ]] && continue

  link = $(echo $line | awk 'N=NF-2 {print $N}')
  file = $(echo $line | awk '{print $NF}')

  # begin OPTION 1) we delete previous link
  rm $link
  ln -s $file $link
  # end OPTION 1)

  # begin OPTION 2) we force the link to be created and replace the older one
  ln -s -f $file $link
  # end OPTION 2)

done  < FILE_WITH_NEW_DATA
fedorqui
  • 275,237
  • 103
  • 548
  • 598
  • the target is to re-create the current link ( for example ) /var/test/info.domain2.com to the new folder - DOMAIN.world.country , according to info.file –  Mar 15 '13 at 12:48
  • OK, then you should delete the link firstly and then re-create it. Let's just add `rm $link` rior to the `ln -s` creation. – fedorqui Mar 15 '13 at 12:49
  • is it possible other way without rm ? –  Mar 15 '13 at 12:51
  • Well, you can also use the `-f` force way with `ln`. This way it would be `ln -s -f $file $link` and will change the existing one. – fedorqui Mar 15 '13 at 12:52
  • I updated my post with both options. Have in mind that when you have a soft link, if you delete it, the linked file is not affected. But be careful if it is a hard link, which would perform differently! – fedorqui Mar 15 '13 at 12:55
  • find / -name -l ( this command not search the hard link ) , so why be worry ? , no chance to found hard link only soft link –  Mar 15 '13 at 14:48
0

Your script is not very efficient. For each file to be mapped, it runs awk twice (which is a minor peccadillo compared with what follows), and then runs find on the entire machine looking for symlinks, and runs ls for each symlink, only selecting the links where the old name shows up.

Traversing the entire file system is excruciatingly slow; you can only afford to do it once.

I think you should use a more complex Perl script (since you're already using Perl) to do most of the work, but you can keep the find command though it would also be possible to use Perl's File::Find (core) module.

The Perl script would be given the mapping file (/tmp/test/info.file) as an argument, and the symlinks from your find command:

  • Read the lines from the file
  • Rename the files accordingly
  • Also record old name and new name in a hash for mapping

  • Read standard input for symlink names

  • Use readlink to read the target of the symlink
  • If the link matches any of the old names
    • Remove the old link
    • Create the new link

The shell script becomes:

find / -type l -print | perl rebuild.links.pl /tmp/test/info.file

The Perl script becomes (warning — untested code):

#!/usr/bin/env perl
use strict;
use warnings;

my %map;

die "Usage: $0 map-file\n" unless @ARGV == 1;

{
    open my $fh, '<', $ARGV[0] or die "Unable to open $ARGV[0] for reading\n";
    while (<$fh>)
    {
        chomp;
        next if m/^\s*$/;  # Skip blank lines
        next if m/^#/;     # Skip comment lines
        my($old, $new) = split /\s+/, $_;
        if (-f $old)
        {
            $map{$old} = $new;
            rename $old, $new or die "Failed to rename $old to $new\n";
        }
    }
    close $fh;
}

shift @ARGV;  # Lose the file name

# Read standard input for symlink names
while (my $source = <>)
{
     chomp $source;
     my $oldlink = readlink $_;
     MAP:
     for my $old (keys %map)
     {
         if ($oldlink =~ m%$old%)
         {
             my $target = $oldlink;
             $target =~ s%$old%$map{$old}%;
             die "Mapped name $target does not exist for symlink $source (old link $oldlink)\n"
                 unless -e $target;
             unlink $source or die "Failed to remove symlink $source\n";
             symlink $target, $source or die "Failed to create symlink $source for $target\n";
             last MAP;
         }
     }
}

The code shown compiles (perl -c) but has not been run. Treat it as an improved outline of what I think your code should look like. Issues that you should make sure you understand before you even think of testing it include:

  • Are the names in the /tmp/test/info.file correct when processed by this script? Are they absolute names, or are they simple names relative to the current directory?
  • Will the mappings shown work correctly?

For debugging the code, I'd probably:

  • Add use constant debug => 1; at the top.
  • Print instead of execute unlink, symlink, rename, etc with the condition if debug;.
  • Run the find command over a sub-directory instead of / until I was sure I knew what was going on.

Be careful — this is dangerous stuff.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • in my perl command ( in find ) i Use Q and E in order to ignore the "." char ( like \. ) , but I not see this option in your code –  Mar 15 '13 at 15:06
  • True; I worked on the (perhaps optimistic) assumption that you don't need to map `DOMAIN.city.country` to `DOMAIN.world.country` without mapping `DOMAINxcityxcountry` to anything. That is, the chances of the `.` acting like a regex `.` and screwing up the matches is negligible to non-existent. If that's a problem, then you need to be careful. As I said, it is only an untested outline; it is not 'working code'. – Jonathan Leffler Mar 15 '13 at 15:09