perl chdir and system commands - perl

I am trying to chdir in perl but I am just not able to get my head around what's going wrong.
This code works.
chdir('C:\Users\Server\Desktop')
But when trying to get the user's input, it doesn't work. I even tried using chomp to remove any spaces that might come.
print "Please enter the directory\n";
$p=<STDIN>;
chdir ('$p') or die "sorry";
system("dir");
Also could someone please explain how I could use the system command in this same situation and how it differs from chdir.
The final aim is to access two folders, check for files that are named the same (eg: if both the folders have a file named "water") and copy the file that has the same name into a third folder.

chdir('$p') tries to change to a directory literally named $p. Drop the single quotes:
chdir($p)
Also, after reading it in, you probably want to remove the newline (unless the directory name really does end with a newline):
$p = <STDIN>;
chomp($p);
But if you are just chdiring to be able to run dir and get the results in your script, you probably don't want to do that. First of all, system runs a command but doesn't capture its output. Secondly, you can just do:
opendir my $dirhandle, $p or die "unable to open directory $p: $!\n";
my #files = readdir $dirhandle;
closedir $dirhandle;
and avoid the chdir and running a command prompt command altogether.

I will use it this way.
chdir "C:/Users/Server/Desktop"
The above works for me

Related

Build array of the contents of the working directory in perl

I am working on a script which utilizes files in surrounding directories using a path such as
"./dir/file.txt"
This works fine, as long as the working directory is the one containing the script. However the script is going out to multiple users and some people may not change their working directory and run the script by typing its entire path like this:
./path/to/script/my_script.pl
This poses a problem as when the script tries to access ./dir/file.txt it is looking for the /dir directory in the home directory, and of course, it can't fine it.
I am trying to utilize readdir and chdir to correct the directory if it isn't the right one, here is what I have so far:
my $working_directory = $ENV{PWD};
print "Working directory: $working_directory\n"; #accurately prints working directory
my #directory = readdir $working_directory; #crashes script
if (!("my_script.pl" ~~ #directory)){ #if my_script.pl isnt in #directoryies, do this
print "Adjusting directory so I work\n";
print "Your old directory: $ENV{PWD}\n";
chdir $ENV{HOME}; #make the directory home
chdir "./path/to/script/my_script.pl"; #make the directory correct
print "Your new directory: $ENV{PWD}\n";
}
The line containing readdir crashes my script with the following error
Bad symbol for dirhandle at ./path/to/script/my_script.pl line 250.
which I find very strange because I am running this from the home directory which prints out properly right beforehand and contains nothing to do with the "bad symbol"
I'm open to any solutions
Thank you in advance
The readdir operates with a directory handle, not a path on a string. You need to do something like:
opendir(my $dh, $working_directory) || die "can't opendir: $!";
my #directory = readdir($dh);
Check perldoc for both readdir and opendir.
I think you're going about this the wrong way. If you're looking for a file that's travelling with your script, then what you probably should consider is the FindBin module - that lets you figure out the path to your script, for use in path links.
So e.g.
use FindBin;
my $script_path = $FindBin::Bin;
open ( my $input, '<', "$script_path/dir/file.txt" ) or warn $!;
That way you don't have to faff about with chdir and readdir etc.

Perl: How to go to the root directory from a script

I want to go to a directory, print in some files, then go back to the root directory.
So, I did this :
chdir "corpus";
open (OUTFILE, ">para$i") or die "Impossible d'ouvrir le fichier\n";
print OUTFILE $tab[$i];
close OUTFILE;
`cd /`;
But it obviously does not work (the cd / part). How do I go back to the root directory once I moved to the child directory in a Perl script?
Thanks a lot :).
Ok, now I have an other issue with this :
for (my $i=0; $i<$number_para;$i++){
open (OUTFILE, ">", "para$i.txt") or die ;
print OUTFILE $tab[$i];
}
worked fine, but when I added the chdir:
for (my $i=0; $i<$number_para;$i++){
chdir "corpus"
open (OUTFILE, ">", "para$i.txt") or die ;
print OUTFILE $tab[$i];
chdir "/"
}
It says "print() on closed filehandle OUTFILE". I don't understand why, since it worked fine before...
chdir "/"
will work just fine. Or if you have a set directory in a variable:
chdir $dir or die $!;
Or as Miller says, you can refer to ... However, you should be aware that you do not have to change directory. If you want to open a file in another directory, you can supply the relative path to it:
open (my $out, ">", "Corpus/para$i") or die $!;
Note that you should use three argument open, with explicit mode, and lexical file handle.
You say root directory, but it looks like you actually just want the parent directory.
To go to the parent directory, use '..';
chdir "..";
Or if you want to be paranoid about cross platform compatability:
use File::Spec;
chdir File::Spec->updir();
To actually go the root directory, just use chdir like you did the first time:
chdir '/';
or once again being paranoid about cross platform compatability:
use File::Spec;
chdir File::Spec->rootdir();
It might be worth pointing out why using cd in backticks didn't work.
Running a command in backticks starts up a completely new shell environment for the command. That new environment starts with a copy of all of the environment variables from the environment that your program is running in. The current directory is one of those environment variables (it's in $ENV{PWD}).
You new environment starts up. The first (and only) thing that it does is to change directory. So the value of $ENV{PWD} in the new environment is changed. But the value in your original environment stays the same as it was.
Your new environment then closes down as its job is done. All of the environment variables that it has are removed from memory. And control returns to the original environment. Which still has the original value for the current directory.
A child environment cannot change the environment variables in its parent environment. So any attempt to change directory using an external program is doomed to failure.
But changing directory using Perl's built-in function chdir works just fine. Because that changes the value in the current environment.
Hope that's helpful.

Perl to set a directory to open, open it, then print the directory opened?

Trying to troubleshoot a port of some perl code from CentOS to Windows.
Really know nothing about Perl, and the code I'm porting is around 700-1000 lines. 100% sure one of the issues I'm seeing is related to how the code is being rendered as a result of being on the OS it's running on.
So, I'm looking for a way to troubleshoot debugging how the OS's are rendering filepath apart from the legacy code; which I can not post to SO due to "IP" reasons.
So, I looking for some perl that I can set a directory to open within the script (for example, C:\data\ or /home/data), then script attempts to load the directory, prints if it failed or succeeded, and then prints the string it attempted to load, regardless if the code failed to open the directory or not.
Open to suggestions, but that's the issue, and the solution I'm seeing.
Questions, feedback, requests - just comment, thanks!!
use IO::Dir;
my $dir = IO::Dir->new($dir_path) or
die "Could not open directory $dir_path: $!\n";
of course, where $dir_path is some path to a directory on your system that you want, either as a var or hard coded. The more 'old school' way would look like:
opendir my $dir, $dir_path or die "Could not open directory $dir_path: $!\n";
That won't print of the directory is opened, but the program will fail if it doesn't open it then print the precise error as to why, which is what the $! variable holds.
Is this what you're looking for?
use DirHandle;
my $dir = "test";
my $dh = new DirHandle($dir);
if($dh) {
print "open directory succeeded\n";
}
else {
print "open directory failed\n";
}
print $dir, "\n";
new DirHandle opens the directory and returns a handle to it. The handle will be undef if the open failed.

How can I scan multiple log files to find which ones have a particular IP address in them?

Recently there have been a few attackers trying malicious things on my server so I've decided to somewhat "track" them even though I know they won't get very far.
Now, I have an entire directory containing the server logs and I need a way to search through every file in the directory, and return a filename if a string is found. So I thought to myself, what better of a language to use for text & file operations than Perl? So my friend is helping me with a script to scan all files for a certain IP, and return the filenames that contain the IP so I don't have to search for the attacker through every log manually. (I have hundreds)
#!/usr/bin/perl
$dir = ".";
opendir(DIR, "$dir");
#files = grep(/\.*$/,readdir(DIR));
closedir(DIR);
foreach $file(#files) {
open FILE, "$file" or die "Unable to open files";
while(<FILE>) {
print if /12.211.23.200/;
}
}
although it is giving me directory read errors. Any assistance is greatly appreciated.
EDIT: Code edited, still saying permission denied cannot open directory on line 10. I am just going to run the script from within the logs directory if you are questioning the directory change to "."
Mike.
Can you use grep instead?
To get all the lines with the IP, I would directly use grep, no need to show a list of files, it's a simple command:
grep 12\.211\.23\.200 *
I like to pipe it to another file and then open that file in an editor...
If you insist on wanting the filenames, it's also easy
grep -l 12\.211\.23\.200 *
grep is available on all Unix//Linux with the GNU tools, or on windows using one of the many implementations (unxutils, cygwin, ...etc.)
You have to concatenate $dirname with $filname when using files found through readdir, remember you haven't chdir'ed into the directory where those files resides.
open FH, "<", "$dirname/$filname" or die "Cannot open $filname:$!";
Incidentally, why not just use grep -r to recursively search all subdirectories under your log dir for your string?
EDIT: I see your edits, and two things. First, this line:
#files = grep(/\.*$/,readdir(DIR));
Is not effective, because you are searching for zero or more . characters at the end of the string. Since it's zero or more, it'll match everything in the directory. If you're trying to exclude files ending in ., try this:
#files = grep(!/\.$/,readdir(DIR));
Note the ! sign for negation if you're trying to exclude those files. Otherwise (if you only want those files and I'm misunderstanding your intent), leave the ! out.
In any case, if you're getting your die message on line 10, most likely you're hitting a file that has permissions such that you can't read it. Try putting the filename in the die output so you can see which file it's failing on:
open FILE, "$file" or die "Unable to open file: $file";
But as with other answers, and to reiterate: Why not use grep? The unix command, not the Perl function.
This will get the file names you are looking for in perl, and probably do it much faster than running and doing a perl regex.
#files = `find ~/ServerLogs -name "*.log" | xargs grep -l "<ip address>"`'
Although, this will require a *nix compliant system, or Cygwin on Windows.
Firstly get a list of files within your source directory:
opendir(DIR, "$dir");
#files = grep(/\.log$/,readdir(DIR));
closedir(DIR);
And then loop through those files
foreach $file(#files)
{
// file processing code
}
My first suggest would be to use grep instead. The right tool for the job, they say...
But to answer your question:
readdir just returns the filenames from the directory. You'll need to concatenate the directory name and filename together.
$path = "$dirname/$filname";
open FH, $path or die ...
Then you should ignore files that are actually directories, such as "." and "..". After getting the $path, check to see if it's a file.
if (-f $path) {
open FH, $path or die ...
while (<FH>)
BTW, I thought I would throw in a mention for File::Next. To iterate over all files in a directory (recursively):
use Path::Class; # always useful.
use File::Next;
my $files = File::Next::files( dir(qw/path to files/) ); # look in path/to/files
while( defined ( my $file = $files->() ) ){
$file = file( $file );
say "Examining $file";
say "found foo" if $file->slurp =~ /foo/;
}
File::Next is taint-safe.
~ doesn't auto-expand in Perl.
opendir my $fh, '~/' or die("Doin It Wrong"); # Doing It Wrong.
opendir my $fh, glob('~/') and die( "Thats right!" );
Also, if you must use readdir(), make sure you guard the expression thus:
while (defined(my $filename = readdir(DH))) {
...
}
If you don't do the defined() test, the loop will terminate if it finds a file called '0'.
Have you looked on CPAN for log parsers? I searched with 'log parse' and it yielded over 200 hits. Some (probably many) won't be relevant - some may be. It depends, in part, on which web server you are using.
Am I reading this right? Your line 10 that gives you the error is
open FILE, "$file" or die "Unable to open files";
And the $file you are trying to read, according to line 6,
#files = grep(/\.*$/,readdir(DIR));
is a file that ends with zero or more dot. Is this what you really wanted? This basically matches every file in the directory, including "." and "..". Maybe you don't have enough permission to open the parent directory for reading?
EDIT: if you only want to read all files (including hidden ones), you might want to use something like the following:
opendir(DIR, ".");
#files = readdir(DIR);
closedir(DIR);
foreach $file (#files) {
if ($file ne "." and $file ne "..") {
open FILE, "$file" or die "cannot open $file\n";
# do stuff with FILE
}
}
Note that this doesn't take care of sub directories.
I know I am way late to this discussion (ran across it while searching for grep related posts) but I am going to answer anyway:
It isn't specified clearly if these are web server logs (Apache, IIS, W3SVC, etc.) but the best tool for mining those for data is the LogParser tool from Microsoft. See logparser.com for more info.
LogParser will allow you to write SQL-like statements against the log files. It is very flexible and very fast.
Use perl from the command line, like a better grep
perl -wnl -e '/12.211.23.200/ and print;' *.log > output.txt
the benefit here is that you can chain logic far easier
perl -wnl -e '(/12.211.23.20[1-11]/ or /denied/i ) and print;' *.log
if you are feeling wacky you can also use more advanced command line options to feed perl one liner result into other perl one liners.
You really need to read "Minimal Perl: For UNIX and Linux People", awesome book on this very sort of thing.
First, use grep.
But if you don't want to, here are two small improvements you can make that I haven't seen mentioned yet:
1) Change:
#files = grep(/\.*$/,readdir(DIR));
to
#files = grep({ !-d "$dir/$_" } readdir(DIR));
This way you will exclude not just "." and ".." but also any other subdirectories that may exist in the server log directory (which the open downstream would otherwise choke on).
2) Change:
print if /12.211.23.200/;
to
print if /12\.211\.23\.200/;
"." is a regex wildcard meaning "any character". Changing it to "\." will reduce the number of false positives (unlikely to change your results in practice but it's more correct anyway).

How do I get a directory listing in Perl? [duplicate]

This question already has answers here:
How do I read in the contents of a directory in Perl?
(9 answers)
Closed 7 years ago.
I would like to execute ls in a Perl program as part of a CGI script. For this I used exec(ls), but this does not return from the exec call.
Is there a better way to get a listing of a directory in Perl?
Exec doesn't return at all. If you wanted that, use system.
If you just want to read a directory, open/read/close-dir may be more appropriate.
opendir my($dh), $dirname or die "Couldn't open dir '$dirname': $!";
my #files = readdir $dh;
closedir $dh;
#print files...
Everyone else seems stuck on the exec portion of the question.
If you want a directory listing, use Perl's built-in glob or opendir. You don't need a separate process.
exec does not give control back to the perl program.
system will, but it does not return the results of an ls, it returns a status code.
tick marks `` will give you the output of our command, but is considered by some as unsafe.
Use the built in dir functions.
opendir, readdir, and so on.
http://perldoc.perl.org/functions/opendir.html
http://perldoc.perl.org/functions/readdir.html
In order to get the output of a system command you need to use backticks.
$listing = `ls`;
However, Perl is good in dealing with directories for itself. I'd recommend using File::Find::Rule.
Yet another example:
chdir $dir or die "Cannot chroot to $dir: $!\n";
my #files = glob("*.txt");
Use Perl Globbing:
my $dir = </dir/path/*>
EDIT: Whoops! I thought you just wanted a listing of the directories... remove the 'directory' call to make this script do what you want it to...
Playing with filehandles is the wrong way to go in my opinion. The following is an example of using File::Find::Rule to find all the directories in a specified directory. It may seem like over kill for what you're doing, but later down the line it may be worth it.
First, my one line solution:
File::Find::Rule->maxdepth(1)->directory->in($base_dir);
Now a more drawn out version with comments. If you have File::Find::Rule installed you should be able to run this no problem. Don't fear the CPAN.
#!/usr/bin/perl
use strict;
use warnings;
# See http://search.cpan.org/~rclamp/File-Find-Rule-0.32/README
use File::Find::Rule;
# If a base directory was not past to the script, assume current working director
my $base_dir = shift // '.';
my $find_rule = File::Find::Rule->new;
# Do not descend past the first level
$find_rule->maxdepth(1);
# Only return directories
$find_rule->directory;
# Apply the rule and retrieve the subdirectories
my #sub_dirs = $find_rule->in($base_dir);
# Print out the name of each directory on its own line
print join("\n", #sub_dirs);
I would recommend you have a look at IPC::Open3. It allows for far more control over the spawned process than system or the backticks do.
On Linux, I prefer find:
my #files = map { chomp; $_ } `find`;