I need to find for a certain dirname, i have a code which greps for the latest-file . Could someone help me to find the ls -ltr dirname* without using the ls -ltr command in the perl script. Below code may help:
my $dir = "/abc/pqr/xyz";
opendir(my $DH, $dir) or die "Error opening $dir: $!";
my %files = map { $_ => (stat("$dir/$_"))[9] } grep(! /^\.\.?$/, readdir($DH));
closedir($DH);
my #sorted_files = sort { $files{$b} <=> $files{$a} } (keys %files);
print "the file is $sorted_files[0] \n";
I need a to find a dir name as new_123 in /abc/pqr/xyz. As ls -ltr new*, as these directories are created everyday so looking for new*.
Its a bit unclear what you are asking for, but:
I need a to find a dir name as new_123 in /abc/pqr/xyz. As ls -ltr
new*, as these directories are created everyday so looking for new*.
For that, you can use glob.
My PERL is a bit rusty but I think this would do it:
while (my $dir = glob("new*")) {
next unless (-d "$dir");
print "$dir\n";
}
If system calls are not a restriction, you can use find:
find /some/path/ -type d -name "new*"
Related
I am having issues getting my Perl script to recognize some subdirectories when traversing through my file system.
Please note that this is part of a homework assignment, and I am unable to use modules of any kind. I have attempted to troubleshoot this on my own for quite some time, and am now at a bit of a roadblock.
I am attempting to traverse a file structure, capture the names of all of the files, directories, and subdirectories into their respective arrays, and print them out in the illustrated format below:
Directory: ./
Files: file1.text file2.pl file3.text
Directories: subdir1 subdir2 subdir3
Directory: subdir1
Files: file3.text file4.pl
Directories: subdir42
...and so on.
I have attempted to use recursion as a solution to this, but my teacher indicated that recursion was not the appropriate way to handle this problem.
I have managed to print, in the appropriate format, the current working directory, and subdirectories within the current working directory.
For some reason, when I change the current code block to
if (-d $entry){
next if $entry =~/^\./;
push(#subdirs,"$entry");
push(#dirs,"$currdir/$entry");
}
elsif(-f $entry) {
push(#files,"$entry");
}
It will omit some of the subdirectories.
Please see the entire script below.
#!/usr/bin/perl
use strict;
use warnings;
sub traverse {
my #dirs = ('.');
my #subdirs;
my #files;
while(my $currdir = shift #dirs){
opendir(my $dirhandle, $currdir) or die $!;
while( my $entry = readdir $dirhandle){
if (-d -r $entry){
next if $entry =~/^\./;
push(#subdirs,"$entry");
push(#dirs,"$entry");
}
else {
push(#files,"$entry");
}
}
print "Directory: $currdir\n";
print "Directories: ";
print "#subdirs";
print"\n";
print "Files: ";
foreach my $curfile (#files) {
next if $curfile eq '.' or $curfile eq '..';
if ($curfile =~ /(\S*\.delete)/){
unlink "$currdir/$curfile";
}
$curfile =~ s/txt$/text/;
print "$curfile ";
}
print "\n";
close $dirhandle;
undef #files;
undef #subdirs;
}
return;
}
traverse();
And the current output:
Directory: .
Directories: test dir_3 test2
Files: recursion.text count_files.pl testing.text thelastone.pl testing.pl prog5_test.pl program51.pl program5.pl recursion.pl recurse.text prog52.pl
dirs.pl
Directory: test
Directories:
Files: testfile1.text prog5_test.pl stilltesting program5.pl testfile2.text dirs.pl
Directory: dir_3
Directories:
Files:
Directory: test2
Directories:
Files: file2.text moretesting file3.text
stilltesting and moretesting should be recognized as directories.
if (-d $entry)
should be
if (-d "$currdir/$entry")
$entry is just a name in a directory. -d needs an actual path.
I'm trying to use the Unix/AIX find command piped to the head command to return the first file in a directory and assign it to a variable. However, all of my attempts have resulted in the all the files that find returns being assigned to the variable without the head command being applied.
Here's are my three attempts:
Attempt 1:
$first_file = `/usr/bin/find $my_path -type f -name $potential_file_names | head -n1`;
Attempt 2:
$first_file = `/usr/bin/find $my_path -type f -name $potential_file_names '|' head -n1`;
Attempt 3:
$first_file = `/usr/bin/find $my_path -type f -name $potential_file_names \\| head -n1`;
The $potential_file_names variable is a string with wildcard characters to return any file in the directory that's in the format "fileXXX.txt" where 'XXX' is a three digit number.
$potential_file_names = 'file???.txt';
The first attempt doesn't work because Perl appears to take exception to the pipe as it returns error, "sh[2]: 0403-057.
First attempt output:
file001.txt
file002.txt
file003.txt
file004.txt
file005.txt
The second and third attempts also fail. The error for them is, "sh[2]: |: not found."
The output for the second and third attempts is the same as the first attempt.
Is it possible to use the find command piped to head to return the first file in the directory I'm searching (in my case, "file001.txt"?
Update
I should mention that the file names may not start with 001, so I'll need the oldest file. The files are created sequentially, so grabbing the first file using find and piping to head -n1 works from the command line outside the script. It needs to be the oldest/first file because I'll be deleting files using a loop later in the script and this needs to find the oldest/first file for each iteration.
Thanks.
Try something like this:
open EXE, qq{/usr/bin/find $my_path -type f -name $potential_file_names | head -n1}
or die qq{Error running command $!};
my $file = <EXE>;
close(EXE);
Avoid using system and backticks when there are pure Perl equivalents; your code will be more portable and you won't have to worry about nasty shell quoting issues.
If you don't care about subdirectories, you can use readdir to get a list of files inside a particular directory:
#!/usr/bin/perl
use strict;
use warnings;
my $dir = 'foo';
opendir my $dh, $dir or die $!;
my #files = sort { -M "$dir/$b" <=> -M "$dir/$a" }
grep { /^file\d{3}\.txt$/ && -f "$dir/$_" } readdir $dh;
closedir $dh;
print $files[0];
This prints the name of the file with the oldest modified date, although you could certainly use another file test instead.
If you also want to search inside subdirectories, you can use File::Find, which is a core module:
use File::Find;
use File::Spec;
my #files;
my $dir = 'foo';
find(sub { push #files, $File::Find::name if /^file\d{3}\.txt$/ and -f $_; }, $dir);
my #sorted = sort { -M $b <=> -M $a } #files;
print $sorted[0];
This prints the path to the file with the oldest modified date.
Okay, some of the answers create a dogs breakfast for follow on coders but do point in the correct direction, with the module 'use File::Find;'
Sample of how I use it.
find (\&wanted, $directory); # start searching the path
sub wanted {
my $file = $File::Find::name;
if (-d $file ) {
$directoryMap{$file} = $file;
return;
}
if (-z $file) {
$zeroHash{$file} = 1;
return;
}
if ($file =~ /(AAF|MXF|NSV|Ogg|RM|SVI|SMI|WMV)$/i) {
my $size = -s $file;
if ($size) {
$hashmap{$file} = $size;
return;
}
else {
$rejectMap{$file} = 1;
return;
}
}
else {
$rejectMap{$file} = 1;
return;
}
}
I use this to look for specific files with a specific extension and then I stuff them into a hash - the whole code an be found in my github in my Perl Diretory (https://github.com/alexmac131/mediaData). you can change the wanted to something useful for you.
Quick note: I've been stuck with this problem for quite a few days and I'm not necessarily hoping to find an answer, but any kind of help that might "enlighten" me. I would also like to mention that I am a beginner in Perl, so my knowledge is not very vast and in this case recursivity is not my forte. here goes:
What I would like my Perl script to do is the following:
take a directory as an argument
go into the directory that was passed and its subdirectories to find an *.xml file
store the full path of the found *.xml file into an array.
Below is the code that i have so far, but i haven't managed to make it work:
#! /usr/bin/perl -W
my $path;
process_files ($path);
sub process_files
{
opendir (DIR, $path) or die "Unable to open $path: $!";
my #files =
# Third: Prepend the full path
map { $path . '/' . $_ }
# Second: take out '.' and '..'
grep { !/^\.{1,2}$/ }
# First: get all files
readdir (DIR);
closedir (DIR);
for (#files)
{
if (-d $_)
{
push #files, process_files ($_);
}
else
{
#analyse document
}
}
return #files;
}
Anybody have any clues to point me in the right direction? Or an easier way to do it?
Thank you,
sSmacKk :D
Sounds like you should be using File::Find. Its find subroutine will traverse a directory recursively.
use strict;
use warnings;
use File::Find;
my #files;
my $path = shift;
find(
sub { (-f && /\.xml$/i) or return;
push #files, $File::Find::name;
}, $path);
The subroutine will perform whatever code it contains on the files it finds. This one simply pushes the XML file names (with full path) onto the #files array. Read more in the documentation for the File::Find module, which is a core module in perl 5.
I would like to remove a broken symlink in a directory using Perl.
In my mind I just had to list the file of a directory and test is this a symlink (-l) and if it returns false just unlink it.
But it appears that when using readir to list all files my broken symlinks are not recoganized as a file. Because my link is pointing to nothing I understand why.
All the file in $myDir are symlinks, either valid or broken.
When I display #files I only get a list of valid symlink.
opendir DIR, $myDir;
my #files = grep(/$regexp/,readdir(DIR));
closedir DIR;
print "filenames : #files\n";
There are two main relevant system calls, stat() and lstat(). The lstat() call will tell you that it is a symlink (but on other files, behaves the same as stat()). This allows you to determine that the name is a symlink. The stat() system call follows a symlink to its end, and tells you about the file (or directory) at the end of the link. If the stat() call fails on the symlink, then the symlink is broken or you're trying to access a directory or file where you have no permission.
The Perl file test operators include -l to detect whether a name is a symlink. You can use the Perl functions stat and lstat explicitly. Between these, you should be able to sort out whether a symlink is broken or not - but you should probably plan to write a function to do the job.
You probably don't need to use the readlink Perl function. Beware the underlying system readlink() call; it does not return a null-terminated string!
It is interesting that neither Perl nor its POSIX module supports the realpath() function. However, the PathTools module does support it. If realpath fails, on a symlink, the symlink is non-functional (aka broken).
Here's some code I've used to remove broken links:
chdir $dir or die;
opendir(DIR, '.') or die;
foreach my $link (readdir DIR) {
next unless -l $link and not -e readlink($link);
print "Removing broken link $link\n";
unlink $link;
}
closedir DIR;
Note that it's important that the directory containing the links is the current directory. readdir returns only filenames, and the links might be relative.
Combining lstat with stat:
say "dangling link at $fn" if (lstat $fn and not stat $fn);
update: it works for me...
salva#topo:~/t/dl$ perl -E 'opendir $dh, "."; say $_ for grep { !stat $_ and lstat $_ } readdir $dh'
foo
salva#topo:~/t/dl$ ls -l
total 0
-rw-rw-r-- 1 salva salva 0 2011-07-05 12:34 f
lrwxrwxrwx 1 salva salva 11 2011-07-05 12:00 fii -> /etc/shadow
lrwxrwxrwx 1 salva salva 12 2011-07-05 11:59 foo -> /etc/hjdkshf
Check for broken symlinks (checking only the top level if there are symlinks to symlinks):
use strict;
use warnings;
use autodie;
opendir my $dirh, '.';
while (my $file = readdir $dirh) {
if ( -l $file ) {
my $target = readlink $file;
if ( ! -e $target && ! -l $target ) {
print "$file -> $target broken\n";
}
}
}
Use readlink() and stat() the result.
Using the built-in Perl glob function ?
For examples:
#files = <*>;
foreach $file (#files) {
print $file . "\n";
}
For a specific $dir:
#files = <$dir*>;
foreach $file (#files) {
print $file . "\n";
}
A broken symlink is a link (-l) that does not exists (!-e)
perl -e 'print "broken: $_\n" for grep { -l and ! -e } glob("*");'
Is there an elegant way in Perl to find the newest file in a directory (newest by modification date)?
What I have so far is searching for the files I need, and for each one get it's modification time, push into an array containing the filename, modification time, then sort it.
There must be a better way.
Your way is the "right" way if you need a sorted list (and not just the first, see Brian's answer for that). If you don't fancy writing that code yourself, use this
use File::DirList;
my #list = File::DirList::list('.', 'M');
Personally I wouldn't go with the ls -t method - that involves forking another program and it's not portable. Hardly what I'd call "elegant"!
Regarding rjray's solution hand coded solution, I'd change it slightly:
opendir(my $DH, $DIR) or die "Error opening $DIR: $!";
my #files = map { [ stat "$DIR/$_", $_ ] } grep(! /^\.\.?$/, readdir($DH));
closedir($DH);
sub rev_by_date { $b->[9] <=> $a->[9] }
my #sorted_files = sort rev_by_date #files;
After this, #sorted_files contains the sorted list, where the 0th element is the newest file, and each element itself contains a reference to the results of stat, with the filename itself in the last element:
my #newest = #{$sorted_files[0]};
my $name = pop(#newest);
The advantage of this is that it's easier to change the sorting method later, if desired.
EDIT: here's an easier-to-read (but longer) version of the directory scan, which also ensures that only plain files are added to the listing:
my #files;
opendir(my $DH, $DIR) or die "Error opening $DIR: $!";
while (defined (my $file = readdir($DH))) {
my $path = $DIR . '/' . $file;
next unless (-f $path); # ignore non-files - automatically does . and ..
push(#files, [ stat(_), $path ]); # re-uses the stat results from '-f'
}
closedir($DH);
NB: the test for defined() on the result of readdir() is because a file called '0' would cause the loop to fail if you only test for if (my $file = readdir($DH))
You don't need to keep all of the modification times and filenames in a list, and you probably shouldn't. All you need to do is look at one file and see if it's older than the oldest you've previously seen:
{
opendir my $dh, $dir or die "Could not open $dir: $!";
my( $newest_name, $newest_time ) = ( undef, 2**31 -1 );
while( defined( my $file = readdir( $dh ) ) ) {
my $path = File::Spec->catfile( $dir, $file );
next if -d $path; # skip directories, or anything else you like
( $newest_name, $newest_time ) = ( $file, -M _ ) if( -M $path < $newest_time );
}
print "Newest file is $newest_name\n";
}
you could try using the shell's ls command:
#list = `ls -t`;
$newest = $list[0];
Assuming you know the $DIR you want to look in:
opendir(my $DH, $DIR) or die "Error opening $DIR: $!";
my %files = map { $_ => (stat("$DIR/$_"))[9] } grep(! /^\.\.?$/, readdir($DH));
closedir($DH);
my #sorted_files = sort { $files{$b} <=> $files{$a} } (keys %files);
# $sorted_files[0] is the most-recently modified. If it isn't the actual
# file-of-interest, you can iterate through #sorted_files until you find
# the interesting file(s).
The grep that wraps the readdir filters out the "." and ".." special files in a UNIX(-ish) filesystem.
If you can't let ls do the sorting for you as #Nathan suggests, then you can optimize your process by only keeping the newest modification time and associated filename seen thus far and replace it every time you find a newer file in the directory. No need to keep any files around that you know are older than the newest one you've seen so far and certainly no need to sort them since you can detect which is the newest one while reading from the directory.
Subject is old, but maybe someone will try it - it isn't portable (Unix-like systems only), but it's quite simple and works:
chdir $directory or die "cannot change directory";
my $newest_file = bash -c 'ls -t | head -1';
chomp $newest_file;
print "$newest_file \n";