I need to change all symbolic links in a given directory to use the shortest relative path.
Example:
change
kat/../kat/link
or
usr/sth/sth/kat/link
into
kat/link
How can I do this using Perl?
You can get a simplified path by using abs_path and then removing the current directory to make it relative:
use warnings;
use strict;
use Cwd qw/getcwd abs_path/;
my $silly_path = 'foo/../foo/../foo/../foo';
my $simplified = abs_path($silly_path);
my $cwd = getcwd();
print "Canonical path: $simplified\n";
print "Current directory: $cwd\n";
$simplified =~ s|^\Q$cwd/||; #Make relative if within current directory.
print "Simplified path: $simplified\n";
This assumes that the links are in Perl's current working directory. You could replace that with another directory if you want. It will result in the relative path for a link within the current directory, or a simplified absolute path for something that points outside the current directory.
You can get all files in a directory using glob, then use the -l $file file test operator to test if $file is a symbolic link.
Related
I am new to perl and want to write a basic program to copy the entire directory contents to another directory. My first hurdle is that I need to give the absolute path for source and destination and I can't seem to get the following code to do that:
use strict;
use warnings;
use File::Copy;
use File::Copy::Recursive qw(fcopy rcopy dircopy fmove rmove dirmove);
my $source_dir = "C:\\Tools\\MyTool\\Scripts";
my $destination_dir = "C:\\Tools\\MyTool\\Scripts_Copy";
fcopy($source_dir,$destination_dir) or die $!;
When I execute this, I get the error that the "No such file or directory"
fcopy is only for copying files, dircopy for copying directories.
Use rcopy which decides to use the appropriate function, depending on whether it has to copy a file or a directory.
For a detailed description, please refer to the CPAN documentation of File::Copy::Recursive.
I have a Perl program to read .html's and only works if the program is in the same directory as the .html's.
I would like to be able to start in different directories and pass the html's location as a parameter. The program (shell example below) traverses the subdirectory "sub"
and its subdirectories to look for .html's, but only works when my perl file is in the same subdirectory "sub". If I put the Perl file
in the home directory, which is one step back from the subdirectory "sub", it doesn't work.
In the shell, if I type "perl project.pl ./sub" from my home directory, it says could
not open ./sub/file1.html. No such file or directory. Yet the file does exist in that exact spot.
file1.html is the first file it is trying to read.
If I change directories in the shell to that subdirectory and move the .pl file
there and then say in the shell: "perl project.pl ./" everything is ok.
To search the directories, I have been using the File::Find concept which I found here:
How to traverse all the files in a directory; if it has subdirectories, I want to traverse files in subdirectories too
Find::File to search a directory of a list of files
#!/usr/bin/perl -w
use strict;
use warnings;
use File::Find;
find( \&directories, $ARGV[0]);
sub directories {
$_ = $File::Find::name;
if(/.*\.html$/){#only read file on local drive if it is an .html
my $file = $_;
open my $info, $file or die "Could not open $file: $!";
while(my $line = <$info>) {
#perform operations on file
}
close $info;
}
return;
}
In the documentation of File::Find it says:
You are chdir()'d to $File::Find::dir when the function is called,
unless no_chdir was specified. Note that when changing to directories
is in effect the root directory (/) is a somewhat special case
inasmuch as the concatenation of $File::Find::dir, '/' and $_ is not
literally equal to $File::Find::name.
So you actually are at ~/sub already. Only use the filename, which is $_. You do not need to overwrite it. Remove the line:
$_ = $File::Find::name;
find changes directory automatically so that $File::Find::name is no longer relative to the current directory.
You can delete this line to get it to work:
$_ = $File::Find::name;
See also File::Find no_chdir.
From the File::Find documentation:
For each file or directory found, it calls the &wanted subroutine.
(See below for details on how to use the &wanted function).
Additionally, for each directory found, it will chdir() into that
directory and continue the search, invoking the &wanted function on
each file or subdirectory in the directory.
(emphasis mine)
The reason it's not finding ./sub/file1.html is because, when open is called, File::Find has already chdired you into ./sub/. You should be able to open the file as just file1.html.
I have a perl script which is using relative file paths.
The relative paths seem to be relative to the location that the script is executed from rather than the location of the perl script. How do I make my relative paths relative to the location of the script?
For instance I have a directory structure
dataFileToRead.txt
->bin
myPerlScript.pl
->output
inside the perl script I open dataFileToRead.txt using the code
my $rawDataName = "../dataFileToRead.txt";
open INPUT, "<", $rawDataName;
If I run the perl script from the bin directory then it works fine
If I run it from the parent directory then it can't open the data file.
FindBin is the classic solution to your problem. If you write
use FindBin;
then the scalar $FindBin::Bin is the absolute path to the location of your Perl script. You can chdir there before you open the data file, or just use it in the path to the file you want to open
my $rawDataName = "$FindBin::Bin/../dataFileToRead.txt";
open my $in, "<", $rawDataName;
(By the way, it is always better to use lexical file handles on anything but a very old perl.)
To turn a relative path into an absolute one you can use Cwd :
use Cwd qw(realpath);
print "'$0' is '", realpath($0), "'\n";
Start by finding out where the script is.
Then get the directory it is in. You can use Path::Class::File's dir() method for this.
Finally you can use chdir to change the current working directory to the directory you just identified.
So, in theory:
chdir(Path::Class::File->new(abs_path($0))->dir());
Relative paths are relative to the current working directory. If you don't have any control over the working directory then you need to find a more robust way to spcecify your file paths, e.g. use absolute paths, or perhaps relative paths which are relative to some specific location within the file system.
I want to uncompress zipped file say, files.zip, to a directory that is different from my working directory.
Say, my working directory is /home/user/address and I want to unzip files in /home/user/name.
I am trying to do it as follows
#!/usr/bin/perl
use strict;
use warnings;
my $files= "/home/user/name/files.zip"; #location of zip file
my $wd = "/home/user/address" #working directory
my $newdir= "/home/user/name"; #directory where files need to be extracted
my $dir = `cd $newdir`;
my #result = `unzip $files`;
But when run the above from my working directory, all the files get unzipped in working directory. How do I redirect the uncompressed files to $newdir?
unzip $files -d $newdir
Use Perl command
chdir $newdir;
and not the backticks
`cd $newdir`
which will just start a new shell, change the directory in that shell, and then exit.
Though for this example, the -d option to unzip is probably the simplest way to do what you want (as mentioned by ennuikiller), for other types of directory-changing, I like the File::chdir module, which allows you to localize directory changes, when combined with the perl "local" operator:
#!/usr/bin/perl
use strict;
use warnings;
use File::chdir;
my $files= "/home/user/name/files.zip"; #location of zip file
my $wd = "/home/user/address" #working directory
my $newdir= "/home/user/name"; #directory where files need to be extracted
# doesn't work, since cd is inside a subshell: my $dir = `cd $newdir`;
{
local $CWD = $newdir;
# Within this block, the current working directory is $newdir
my #result = `unzip $files`;
}
# here the current working directory is back to what it was before
You can also use the Archive::Zip module. Look specifically at the extractToFileNamed:
"extractToFileNamed( $fileName )
Extract me to a file with the given name. The file will be created with default modes. Directories will be created as needed. The $fileName argument should be a valid file name on your file system. Returns AZ_OK on success. "
I am struggling with a method of walking a directory tree to check existence of a file in multiple directories. I am using Perl and I only have the ability to use File::Find as I am unable to install any other modules for this.
Here's the layout of the file system I want to traverse:
Cars/Honda/Civic/Setup/config.txt
Cars/Honda/Pathfinder/Setup/config.txt
Cars/Toyota/Corolla/Setup/config.txt
Cars/Toyota/Avalon/Setup/
Note that the last Setup folder is missing a config.txt file.
Edit: also, in each of the Setup folders there are a number of other files as well that vary from Setup folder to Setup folder. There really isn't any single file to search against to get into the Setup folder itself.
So you can see that the file path stays the same except for the make and model folders. I want to find all of the Setup folders and then check to see if there is a config.txt file in that folder.
At first I was using the following code with File::Find
my $dir = '/test/Cars/';
find(\&find_config, $dir);
sub find_config {
# find all Setup folders from the given top level dir
if ($File::Find::dir =~ m/Setup/) {
# create the file path of config.txt whether it exists or not, well check in the next line
$config_filepath = $File::Find::dir . "/config.txt";
# check existence of file; further processing
...
}
}
You can obviously see the flaw in trying to use $File::Find::dir =~ m/Setup/ since it will return a hit for every single file in the Setup folder. Is there any way to use a -d or some sort of directory check rather than a file check? The config.txt is not always in the folder (I will need to create it if it doesn't exist) so I can't really use something like return unless ($_ =~ m/config\.txt/) since I don't know if it's there or not.
I'm trying to find a way to use something like return unless ( <is a directory> and <the directory has a regex match of m/Setup/>).
Maybe File::Find is not the right method for something like this but I've been searching around for a while now without any good leads on working with directory names rather than file names.
File::Find finds directory names, too. You want to check for when $_ eq 'Setup' (note: eq, not your regular expression, which would also match XXXSetupXXX), and then see if there's a config.txt file in the directory ( -f "$File::Find::name/config.txt" ). If you want to avoid complaining about files named Setup, check that the found 'Setup' is a directory with -d.
I'm trying to find a way to use something like return unless ( <is a directory> and <the directory has a regex match of m/Setup/>).
use File::Spec::Functions qw( catfile );
my $dir = '/test/Cars/';
find(\&find_config, $dir);
sub find_config {
return unless $_ eq 'Setup' and -d $File::Find::name;
my $config_filepath = catfile $File::Find::name => 'config.txt';
# check for existence etc
}