Perl list files under multiple directories - perl

I've currently got this to grab all files under Assets/Editor
#files = bsd_glob( "Assets/Editor/postprocessbuildplayer_*", GLOB_NOCASE );
But I would like to access all files starting with postprocessbuildplayer_ starting from Assets as my root folder.
Example:
Assets/Temp/Editor/PostprocessBuildPlayer_DWARF
Assets/Directory_1/Editor/PostprocessBuildPlayer_1
Assets/Editor/PostprocessBuildPlayer_Default
The entire script should anyone know a better way:
#!/usr/bin/perl
# Post Process Build Player -- Master
# Searches for other PostprocessBuildPlayer scripts and executes them. Make sure the other script
# have a name suffix with an underscore "_" like "PostprocessBuildPlayer_AnotherBuild" or whatever.
#
# Based on script by Rob Terrell, rob#stinkbot.com
use File::Glob ':glob';
# Grab all the PostprocessBuildPlayer files
#files = bsd_glob( "Assets/Editor/postprocessbuildplayer_*", GLOB_NOCASE );
foreach $file( #files )
{
if( !( $file =~ m/\./ ) )
{
system( "chmod", "755", $file );
print "PostProcessBuildPlayer: calling " . $file . "\n";
system( $file, $ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3], $ARGV[4], $ARGV[5], $ARGV[6] );
if ( $? == -1 )
{
print "command failed: $!\n";
}
else
{
printf "command exited with value %d", $? >> 8;
}
}
}

Use File::Find to recurse a directory tree
use strict;
use warnings;
use File::Find;
my #files;
find(sub {
push #files, File::Find::name if /^PostprocessBuildPlayer/;
}, 'Assets/');

Related

Perl check if filename matching a pattern exists recursively

I'm looping through folders in a directory and need to check if a file that matches a pattern exists in each directory. I've used glob but it seems to work for the the first folder only. I get file not found for the second folder even I know that it's there.
Here is my code:
my #dirs = grep { -d } glob '/data/test_all_runs/*';
for my $dir ( #dirs ) {
print "the directory is $dir\n";
my $run_folder = (split '/', $dir)[3];
print "the folder is $run_folder\n";
my $matrix_excel = $dir."/*bcmatrix.xls";
my $summary_excel = $dir."/*bc_summary.xls";
unless (-e $summary_excel) {
if (glob($summary_excel)) {
At least one file matches "*.file"
}
else
{
print "File Doesn't Exist!";
print STDERR "|=============================================|\n";
print STDERR "| |\n";
print STDERR "| Can't find Summary .xls File!!! |\n";
print STDERR "| |\n";
print STDERR "| Upload the file and rerun the program. |\n";
print STDERR "| |\n";
print STDERR "|=============================================|\n";
die;
}
}
Is there another method to check if *bcmatrix.xls file exists in each folder of /data/test_all_runs/*?
This may be a bit overkill, but it seems to do what you need. I use File::Find::Rule to fetch all of the directories in the directory structure, then use glob to get the list of file names that match the pattern:
Given this directory structure:
orig
|-a
|-a.txt
|-b
|-ba.txt
|-c
With this code:
use warnings;
use strict;
use File::Basename;
use File::Find::Rule;
my $dir = 'orig';
my $file = 'a.txt';
my #dirs = File::Find::Rule->directory
->in($dir);
for (#dirs){
next if /(?:\.|\.\.)/;
if (my #files = glob "$_/*$file"){
for my $path (#files){
my $name = basename $path;
print "file $name exists in $_\n";
}
}
else {
print "file not found in directory $_\n";
}
}
I get the following output:
file not found in directory orig
file ba.txt exists in orig/b
file not found in directory orig/c
file a.txt exists in orig/a
I suggest that you use something like this. It will build a hash of arrays that lists all the files in each subdirectory of /data/test_all_runs that look like either *bcmatrix.xls or *bc_summary.xls
You should be able to do what you want with the result
use strict;
use warnings 'all';
use File::Spec::Functions 'splitdir';
my %files;
for my $path ( glob '/data/test_all_runs/*/*{bcmatrix,bc_summary}.xls' ) {
my ($subdir, $file) = (splitdir $path)[-2, -1];
push #{ $files{$subdir} }, $file;
}
use Data::Dumper;
print Dumper \%files;

Print files and subdirectories of given directory

I am trying to get all files and directories from a given directory but I can't specify what is the type (file/ directory). Nothing is being printed. What I am doing wrong and how to solve it. Here is the code:
sub DoSearch {
my $currNode = shift;
my $currentDir = opendir (my $dirHandler, $currNode->rootDirectory) or die $!;
while (my $node = readdir($dirHandler)) {
if ($node eq '.' or $node eq '..') {
next;
}
print "File: " . $node . "\n" if -f $node;
print "Directory " . $node . "\n" if -d $node;
}
closedir($dirHandler);
}
readdir returns only the node name without any path information. The file test operators will look in the current working directory if no path is specified, and because the current directory isn't $currNode->rootDirectory they won't be found
I suggest you use rel2abs from the File::Spec::Functions core module to combine the node name with the path. You can use string concatenation, but the library function takes care of corner cases like whether the directory ends with a slash
It's also worth pointing out that Perl identifiers are most often in snake_case, and people familiar with the language would thank you for not using capital letters. They should especially be avoided for the first character of an identifier, as names like that are reserved for globals like package names
I think your subroutine should look like this
use File::Spec::Functions 'rel2abs';
sub do_search {
my ($curr_node) = #_;
my $dir = $curr_node->rootDirectory;
opendir my $dh, $dir or die qq{Unable to open directory "$dir": $!};
while ( my $node = readdir $dh ) {
next if $node eq '.' or $node eq '..';
my $fullname = rel2abs($node, $dir);
print "File: $node\n" if -f $fullname;
print "Directory $node\n" if -d $fullname;
}
}
An alternative method is to set the current working directory to the directory being read. That way there is no need to manipulate file paths, but you would need to save and restore the original working directory before and after changing it
The Cwd core module provides getcwd and your code would look like this
use Cwd 'getcwd';
sub do_search {
my ($curr_node) = #_;
my $cwd = getcwd;
chdir $curr_node->rootDirectory or die $!;
opendir my $dh, '.' or die $!;
while ( my $node = readdir $dh ) {
next if $node eq '.' or $node eq '..';
print "File: \n" if -f $node;
print "Directory $node\n" if -d $node;
}
chdir $cwd or die $!;
}
Use this CPAN Module to get all files and subdirectories recursively.
use File::Find;
find(\&getFile, $dir);
my #fileList;
sub getFile{
print $File::Find::name."\n";
# Below lines will print only file name.
#if ($File::Find::name =~ /.*\/(.*)/ && $1 =~ /\./){
#push #fileList, $File::Find::name."\n";
}
Already answered, but sometimes is handy not to care with the implementation details and you could use some CPAN modules for hiding such details.
One of them is the wonderful Path::Tiny module.
Your code could be as:
use 5.014; #strict + feature 'say' + ...
use warnings;
use Path::Tiny;
do_search($_) for #ARGV;
sub do_search {
my $curr_node = path(shift);
for my $node ($curr_node->children) {
say "Directory : $node" if -d $node;
say "Plain File : $node" if -f $node;
}
}
The children method excludes the . and the .. automatically.
You also need understand that the -f test is true only for the real files. So, the above code excludes for example symlinks (whose points to real files), or FIFO files, and so on... Such "files" could be usually opened and read as plain files, therefore somethimes instead of the -f is handy to use the -e && ! -d test (e.g. exists, but not an directory).
The Path::Tiny has some methods for this, e.g. you could write
for my $node ($curr_node->children) {
print "Directory : $node\n" if $node->is_dir;
print "File : $node\n" if $node->is_file;
}
the is_file method is usually DWIM - e.g. does the: -e && ! -d.
Using the Path::Tiny you could also easily extend your function to walk the whole tree using the iterator method:
use 5.014;
use warnings;
use Path::Tiny;
do_search($_) for #ARGV;
sub do_search {
#maybe you need some error-checking here for the existence of the argument or like...
my $iterator = path(shift)->iterator({recurse => 1});
while( my $node = $iterator->() ) {
say "Directory : ", $node->absolute if $node->is_dir;
say "File : ", $node->absolute if $node->is_file;
}
}
The above prints the type for all files and directories recursive down from the given argument...
And so on... the Path::Tiny is really worth to have installed.

How can I compare file list from a tar archive and directory?

I am still learning Perl. Can anyone please suggest me the Perl code to compare files from .tar.gz and a directory path.
Let's say I have tar.gz backup of following directory path which I have taken few days back.
a/file1
a/file2
a/file3
a/b/file4
a/b/file5
a/c/file5
a/b/d/file and so on..
Now I want to compare files and directories under this path with the tar.gz backup file.
Please suggest Perl code to do that.
See Archive::Tar.
The Archive::Tar and File::Find modules will be helpful. A basic example is shown below. It just prints information about the files in a tar and the files in a directory tree.
It was not clear from your question how you want to compare the files. If you need to compare the actual content, the get_content() method in Archive::Tar::File will likely be needed. If a simpler comparison is adequate (for example, name, size, and mtime), you won't need much more than methods used in the example below.
#!/usr/bin/perl
use strict;
use warnings;
# A utility function to display our results.
sub Print_file_info {
print map("$_\n", #_), "\n";
}
# Print some basic information about files in a tar.
use Archive::Tar qw();
my $tar_file = 'some_tar_file.tar.gz';
my $tar = Archive::Tar->new($tar_file);
for my $ft ( $tar->get_files ){
# The variable $ft is an Archive::Tar::File object.
Print_file_info(
$ft->name,
$ft->is_file ? 'file' : 'other',
$ft->size,
$ft->mtime,
);
}
# Print some basic information about files in a directory tree.
use File::Find;
my $dir_name = 'some_directory';
my #files;
find(sub {push #files, $File::Find::name}, $dir_name);
Print_file_info(
$_,
-f $_ ? 'file' : 'other',
-s,
(stat)[9],
) for #files;
Perl is kind of overkill for this, really. A shell script would do fine. The steps you need to take though:
Extract the tar to a temporary folder somewhere.
diff -uR the two folders and redirect the output somewhere (or perhaps pipe to less as appropriate)
Clean up the temporary folder.
And you're done. Shouldn't be more than 5-6 lines. Something quick and untested:
#!/bin/sh
mkdir $TEMP/$$
tar -xz -f ../backups/backup.tgz $TEMP/$$
diff -uR $TEMP/$$ ./ | less
rm -rf $TEMP/$$
Heres an example that checks to see if every file that is in an archive, also exists in a folder.
# $1 is the file to test
# $2 is the base folder
for file in $( tar --list -f $1 | perl -pe'chomp;$_=qq["'$2'$_" ]' )
do
# work around bash deficiency
if [[ -e "$( perl -eprint$file )" ]]
then
echo " $file"
else
echo "no $file"
fi
done
This is how I tested this:
I removed / renamed config, then ran the following:
bash test Downloads/update-dnsomatic-0.1.2.tar.gz Downloads/
Which gave the output of:
"Downloads/update-dnsomatic-0.1.2/"
no "Downloads/update-dnsomatic-0.1.2/config"
"Downloads/update-dnsomatic-0.1.2/update-dnsomatic"
"Downloads/update-dnsomatic-0.1.2/README"
"Downloads/update-dnsomatic-0.1.2/install.sh"
I am new to bash / shell programming, so there is probably a better way to do this.
This might be a good starting point for a good Perl program. It does what the question asked for though.
It was just hacked together, and ignores most of the best practices for Perl.
perl test.pl full \
Downloads/update-dnsomatic-0.1.2.tar.gz \
Downloads/ \
update-dnsomatic-0.1.2
#! /usr/bin/env perl
use strict;
use 5.010;
use warnings;
use autodie;
use Archive::Tar;
use File::Spec::Functions qw'catfile catdir';
my($action,$file,$directory,$special_dir) = #ARGV;
if( #ARGV == 1 ){
$file = *STDOUT{IO};
}
if( #ARGV == 3 ){
$special_dir = '';
}
sub has_file(_);
sub same_size($$);
sub find_missing(\%$);
given( lc $action ){
# only compare names
when( #{[qw'simple name names']} ){
my #list = Archive::Tar->list_archive($file);
say qq'missing file: "$_"' for grep{ ! has_file } #list;
}
# compare names, sizes, contents
when( #{[qw'full aggressive']} ){
my $next = Archive::Tar->iter($file);
my( %visited );
while( my $file = $next->() ){
next unless $file->is_file;
my $name = $file->name;
$visited{$name} = 1;
unless( has_file($name) ){
say qq'missing file: "$name"' ;
next;
}
unless( same_size( $name, $file->size ) ){
say qq'different size: "$name"';
next;
}
next unless $file->size;
unless( same_checksum( $name, $file->get_content ) ){
say qq'different checksums: "$name"';
next;
}
}
say qq'file not in archive: "$_"' for find_missing %visited, $special_dir;
}
}
sub has_file(_){
my($file) = #_;
if( -e catfile $directory, $file ){
return 1;
}
return;
}
sub same_size($$){
my($file,$size) = #_;
if( -s catfile($directory,$file) == $size ){
return $size || '0 but true';
}
return; # empty list/undefined
}
sub same_checksum{
my($file,$contents) = #_;
require Digest::SHA1;
my($outside,$inside);
my $sha1 = Digest::SHA1->new;
{
open my $io, '<', catfile $directory, $file;
$sha1->addfile($io);
close $io;
$outside = $sha1->digest;
}
$sha1->add($contents);
$inside = $sha1->digest;
return 1 if $inside eq $outside;
return;
}
sub find_missing(\%$){
my($found,$current_dir) = #_;
my(#dirs,#files);
{
my $open_dir = catdir($directory,$current_dir);
opendir my($h), $open_dir;
while( my $elem = readdir $h ){
next if $elem =~ /^[.]{1,2}[\\\/]?$/;
my $path = catfile $current_dir, $elem;
my $open_path = catfile $open_dir, $elem;
given($open_path){
when( -d ){
push #dirs, $path;
}
when( -f ){
push #files, $path, unless $found->{$path};
}
default{
die qq'not a file or a directory: "$path"';
}
}
}
}
for my $path ( #dirs ){
push #files, find_missing %$found, $path;
}
return #files;
}
After renaming config to config.rm, adding an extra char to README, changing a char in install.sh, and adding a file .test. This is what it outputted:
missing file: "update-dnsomatic-0.1.2/config"
different size: "update-dnsomatic-0.1.2/README"
different checksums: "update-dnsomatic-0.1.2/install.sh"
file not in archive: "update-dnsomatic-0.1.2/config.rm"
file not in archive: "update-dnsomatic-0.1.2/.test"

How do I search for .exe files

Do you guys have an idea on how to search or list down .exe files on the server
I am currently using (or maybe place it in an array)?
I will use this command in my Perl program. Assuming that my program is also located on the said server.
My OS is Linux - Ubuntu if that even matters, just in case. Working in CLI here. =)
As mentioned, It is not clear whether you want '*.exe' files, or executable files.
You can use File::Find::Rule to find all executable files.
my #exe= File::Find::Rule->executable->in( '/'); # all executable files
my #exe= File::Find::Rule->name( '*.exe')->in( '/'); # all .exe files
If you are looking for executable files, you (the user running the script) need to be able to execute the file, so you probably need to run the script as root.
It might take a long time to run to.
If you are looking for .exe files, chances are that your disk is already indexed by locate. So this would be much faster:
my #exe= `locate \.exe | grep '\.exe$'`
Perl to find every file under a specified directory that has a .exe suffix:
#!/usr/bin/perl
use strict;
use File::Spec;
use IO::Handle;
die "Usage: $0 startdir\n"
unless scalar #ARGV == 1;
my $startdir = shift #ARGV;
my #stack;
sub process_file($) {
my $file = shift;
print $file
if $file =~ /\.exe$/io;
}
sub process_dir($) {
my $dir = shift;
my $dh = new IO::Handle;
opendir $dh, $dir or
die "Cannot open $dir: $!\n";
while(defined(my $cont = readdir($dh))) {
next
if $cont eq '.' || $cont eq '..';
my $fullpath = File::Spec->catfile($dir, $cont);
if(-d $fullpath) {
push #stack, $fullpath
if -r $fullpath;
} elsif(-f $fullpath) {
process_file($fullpath);
}
}
closedir($dh);
}
if(-f $startdir) {
process_file($startdir);
} elsif(-d $startdir) {
#stack = ($startdir);
while(scalar(#stack)) {
process_dir(shift(#stack));
}
} else {
die "$startdir is not a file or directory\n";
}
Have a look at File::Find.
Alternatively, if you can come up with a command line to the *nix file command, you can use find2perl to convert that command line to a Perl snippet.
I'll probably be shot down for suggesting this, but you don't have to use modules for a simple task. For example:
#!/usr/bin/perl -w
#array = `find ~ -name '*.exe' -print`;
foreach (#array) {
print;
}
Of course, it will need to have some tweaking for your particular choice of starting directory (here, I used ~ for the home directory)
EDIT: Maybe I should have said until you get the modules installed
to get recursively use
use File::Find;
##cal the function by sending your search dir and type of the file
my #exe_files = &get_files("define root directory" , ".exe");
##now in #exe_files will have all .exe files
sub get_files() {
my ($location,$type) = #_;
my #file_list;
if (defined $type) {
find (sub { my $str = $File::Find::name;
if($str =~ m/$type/g ) {
push #file_list, $File::Find::name ;
}
}, $location);
} else {
find (sub {push #file_list, $File::Find::name }, $location);
}
return (#file_list);
}

How can I recursively copy the contents of directory using Perl?

I am running the current version of ActivePerl on Windows Vista, and I was wondering if you could show me the best and simplest way to copy a folder and it's contents to another location. Contents would include various files, and most likely some more nested folders.
I imagine there must be a module out there somewhere that I don't know about that does this - but if there is a simple homebrew type of solution I'd like to see that also.
Take a look at File::Copy::Recursive.
If you are simply copying and not doing any processing on the files, there is no reason not to use xcopy.
Now, I wrote the script below in light of Telemachus's comments to provide you with a starting point. I personally would stick with xcopy for copying and File::Find if file contents need to be processed. Besides, I am sure there are about 37 bugs in the code below. But, if you want to play around, it might be instructive:
#!/usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions qw( catfile );
die "mydeepcp src-dir target-dir\n" unless #ARGV == 2;
my ($src, $target) = #ARGV;
mydeepcp( $src => $target );
sub mydeepcp {
my ($src, $target) = #_;
opendir my $dir_h, $src
or die "Cannot open directory: '$src': $!";
while ( my $file = readdir $dir_h ) {
next if $file =~ m{^\.\.?$};
my $src_path = catfile $src => $file;
my $target_path = catfile $target => $file;
if ( -d $src_path ) {
# FIXME: insert code somewhere to create destination dir
mydeepcp($src_path => $target_path);
}
elsif ( -f _ ) {
if ( my $err = docp($src_path => $target_path) ) {
warn sprintf(
"Error copying '%s' from '%s' to '%s': %s\n",
$file, $src, $target, $err
);
}
}
else {
warn "Skipping '$src_path'\n";
}
}
closedir $dir_h;
return;
}
sub docp {
my ($src, $target) = #_;
warn "'$src' => '$target'\n";
return;
}
__END__
Output:
C:\Temp> ghj c:\windows f:\qwert
...
'C:\windows\$hf_mig$\KB899591\update\spcustom.dll' => 'F:\qwert\$hf_mig$\KB899591\update\spcustom.dll'
'C:\windows\$hf_mig$\KB899591\update\update.exe' => 'F:\qwert\$hf_mig$\KB899591\update\update.exe'
'C:\windows\$hf_mig$\KB899591\update\update.ver' => 'F:\qwert\$hf_mig$\KB899591\update\update.ver'