print only one/desired value in for/foreach loop - perl

I have part of the code (program which needs to calculate sum of open files on system for specified users):
for my $opt_u (#opt_u){
my $generic_acc_open = `/usr/sbin/lsof -u $opt_u | /usr/bin/wc -l`;
chomp ($generic_acc_open);
#print "$open";
print "Number of open files for users:$opt_u[0]=$generic_acc_open**$opt_u[1]=$generic_acc_open\n;"
}
where opt_u is argument for users specified on cli.
My problem is when i run a program (./proc_limit -u root jenkins) i am getting output like this:
Number of open files for users:root=85**jenkins=85
;Number of open files for users:root=13**jenkins=13
I am trying to get output in one line, probably it is not possible because array is specified with arguments in this case two times(for two users). Is it possible with for/foreach loop or i should use something else to get output in one line like this:
Number of open files for users:root=85**jenkins=13

You're currently trying to print out results for two different queries using the same variable, $generic_acc_open.
You need to get the results for each user and store them separately. Here's one possible way to do it that will work for any number of users:
print "Number of open files for users: ",
join(" ** ",
map { my $n = `/usr/sbin/lsof -u $_ | /usr/bin/wc -l`;
$n =~ s/\s+//g;
"$_ = $n"
} #opt_u ), "\n";
Output:
Number of open files for users: anonymous = 5548 ** jenkins = 42 ** root = 0
Explanation:
print "Number of open files for users: ",
# join every member of the array with " ** "
join(" ** ",
# map applies the expressions within the braces to each member of the array #opt_u
# map produces an array as output, which is acted upon by the join function
map {
# get number of open files for user $_
my $n = `/usr/sbin/lsof -u $_ | /usr/bin/wc -l`;
# remove whitespace from the answer
$n =~ s/\s+//g;
# print out the user, $_, and the number of open files, $n
"$_ = $n" } #opt_u ),
"\n";
To print the total number of files, keep a tally of how many files are open and print it at the end of the line:
my $sum;
print "Number of open files for users: ",
join(" ** ",
map { my $n = `/usr/sbin/lsof -u $_ | /usr/bin/wc -l`;
$n =~ s/\s+//g;
$sum += $n;
"$_ = $n"
} #opt_u ), "; total files: $sum\n";

print "Number of open files for users:" ;
for my $opt_u (#opt_u){
my $generic_acc_open = `/usr/sbin/lsof -u $opt_u | /usr/bin/wc -l`;
chomp ($generic_acc_open);
print " $opt_u=$generic_acc_open";
}
print "\n";

Related

Perl search for a content in file and take out value using regex

I have a set of log files where I want to search for a word called Sum in each file and take the respective sum value out which is next to Sum keyword in the file.
Instead of doing file read operation I am using Tie::File to have the content of file in array and thinking to take out whatever value I needed.
Here is my code:
...
my $logpath = "C:/Users/Vinod/Perl/LOG/";
opendir(DIR, $logpath);
while (my $file = readdir(DIR)) {
next unless (-f "$logpath/$file");
next unless ($file =~ m/\.log$/);
my #lines;
print "$file\n";
tie #lines, 'Tie::File', $file, mode => O_RDWR;
for (#lines) {
print $_ if($_ =~ m/Sum/);
}
untie #lines;
}
closedir(DIR);
Here is what I am trying to extract from my log file:
test_log_file.log
....
....
=
> Sum: 10 PC's, 5 UPS's
End...
From the above test_log_file.log I want to take out value 10.
But the line print $_ if($_ =~ m/Sum/); is printing entire file content. No idea how I can take out the line which contains Sum and PC keywords. So that I can have value 10 using regex.
I can able to take out Sum value using below command:
$sum = qx/more $file | grep -i 'Sum' | grep 'PC' | awk -F " " '{print \$3}'/;
But wanted to resolve this using Perl script itself.
Read line by line. Capture the number and output only the captured part:
while (<>) { say $1 if /Sum: ([0-9]+)/ }

how to count a repeating string in a line using perl

I have the below file
file1:
abc def host 123 host 869 host
I wrote below script to count the occurrence of a "host" keyword in each line.
I tried all the ways(refer the ones which are commented) still it does not seem to work. sed command worked in command line but not inside the perl script
#!/usr/bin/perl
open(SOURCE,"</home/amp/surevy01/file1");
open(DESTINATION,"</home/amp/surevy01/file2");
while(my $line = <SOURCE>)
{
while(my $line1 = <DESTINATION>)
{
#chomp($line);
#chomp($line1);
if ($line =~ "host")
{
#my $count = grep {host} $line;
#my $count = `sed -i {s/host/host\n/g} $line1 | grep -c {host}`;
#my $count = `perl -pi -e 's/host/host\n/g' $line1 | grep -c host`;
#my $count grep ("host" ,$line);
print "$count";
print "match found \n";
next;
}
else
{
print "match not found \n";
exit;
}
}
}
I'm a beginner to perl. Looking for your valuable suggestions
Your own solution will match instances like hostages and Shostakovich
grep is the canonical way to count elements of a list, and split will turn your line into a list of words, giving
my $count = grep { $_ eq 'host' } split ' ', $line
I don't know why you're looping through two files in your example, but you can use the /g (global) flag:
my $line = "abc def host 123 host 869 host";
my $x = 0;
while ($line =~ /host/g){
$x++;
}
print "$x\n"; # 3
When you run a regex with /g in scalar context (as is the conditional in the while statement), it will keep track of the location of the last match and restart from there. Therefore, /host/g in a loop as above will find each occurence of host. You can also use the /g in list contexts:
my $line = "abc def host 123 host 869 host";
my #matches = $contents =~ /host/g;
print scalar #matches; # 3 again
In this case, #matches will contain all matches of the regexp against the string, which will be ('host', 'host', 'host') since the query is a simple string. Then, scalar(#matches) will yield the length of the list.
This produces the number of instances of host in $line:
my $count = () = $line =~ /host/g;
But that also matches hosting. To avoid that, the following will probably do the trick:
my $count = () = $line =~ /\bhost\b/g;
=()= this is called Perl secret Goatse operator. More info

Perl change column delimiter of a file

I stored the output of uniq -c into two files $lfile and $lfile2, I tried to make column separator as " " with tr command, but it seems not working, after split of $line there nothing get stored in $count, $e_code.
How to split the $line in two parts?
`egrep -o [A-Z]{3}[0-9]{5} $e_file |sort|uniq -c |sort -nrk1 |head -15 >$lfile1`;
`egrep -o [A-Z]{3}[0-9]{5} $y_file |sort|uniq -c |sort -nrk1 |head -150 >$lfile2`;
open (IN, "<$lfile1") ;
foreach $line (<IN>)
{
my $f_line=`echo $line|tr -s ' ' ` ;
print "$f_line \n" ;
my ($count, $e_code) = split / /, $f_line;
uniq -c produces output similar to this:
2 ABC12345
1 ZXC09876
Notice the leading spaces. Apparently, you intended to strip the leading spaces but keeping the space in between is vital for split / /, $f_line; to succeed.
To remove the leading spaces only use ^\s+ pattern (^ is the start of line anchor) and pass it to s/// substitution operator:
$line =~ s/^\s+//;
Please note you may accomplish this task in pure Perl:
my %counts = ();
open(my $fh, $e_file) or die "Failed to open $e_file: $!";
while (<$fh>) {
# collect counts of each [A-Z]{3}[0-9]{5} match in the %counts
# hash with the match being a kay in this hash and the number
# of occurrences of this match being the value
$counts{$1}++ foreach /([A-Z]{3}[0-9]{5})/g;
}
# iterate through the first 15 top encountered matches
foreach my $key (
(
sort {$counts{$b} <=> $counts{$a}} keys %counts # sort dictionary keys
# in value descending order
)[0..14] # take first 15 items of the ordered list
)
{
print "$key $counts{$key}\n";
}
Demo: https://ideone.com/eN1AyJ

zcat working in command line but not in perl script

Here is a part of my script:
foreach $i ( #contact_list ) {
print "$i\n";
$e = "zcat $file_list2| grep $i";
print "$e\n";
$f = qx($e);
print "$f";
}
$e prints properly but $f gives a blank line even when $file_list2 has a match for $i.
Can anyone tell me why?
Always is better to use Perl's grep instead of using pipe :
#lines = `zcat $file_list2`; # move output of zcat to array
die('zcat error') if ($?); # will exit script with error if zcat is problem
# chomp(#lines) # this will remove "\n" from each line
foreach $i ( #contact_list ) {
print "$i\n";
#ar = grep (/$i/, #lines);
print #ar;
# print join("\n",#ar)."\n"; # in case of using chomp
}
Best solution is not calling zcat, but using zlib library :
http://perldoc.perl.org/IO/Zlib.html
use IO::Zlib;
# ....
# place your defiiniton of $file_list2 and #contact list here.
# ...
$fh = new IO::Zlib; $fh->open($file_list2, "rb")
or die("Cannot open $file_list2");
#lines = <$fh>;
$fh->close;
#chomp(#lines); #remove "\n" symbols from lines
foreach $i ( #contact_list ) {
print "$i\n";
#ar = grep (/$i/, #lines);
print (#ar);
# print join("\n",#ar)."\n"; #in case of using chomp
}
Your question leaves us guessing about many things, but a better overall approach would seem to be opening the file just once, and processing each line in Perl itself.
open(F, "zcat $file_list |") or die "$0: could not zcat: $!\n";
LINE:
while (<F>) {
######## FIXME: this could be optimized a great deal still
foreach my $i (#contact_list) {
if (m/$i/) {
print $_;
next LINE;
}
}
}
close (F);
If you want to squeeze out more from the inner loop, compile the regexes from #contact_list into a separate array before the loop, or perhaps combine them into a single regex if all you care about is whether one of them matched. If, on the other hand, you want to print all matches for one pattern only at the end when you know what they are, collect matches into one array per search expression, then loop them and print when you have grepped the whole set of input files.
Your problem is not reproducible without information about what's in $i, but I can guess that it contains some shell metacharacter which causes it to be processed by the shell before the grep runs.

Prompt user for input then add and subtract in the corresponding field [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I want the variable $cpySold to be subtracted from $a[3] and added to $a[4]. How can I do it?
Currently my output is as follows:
Title:Alice in wonderland
Author:robert
No Of Copies Sold:*3*
Current Book Info:
Alice in wonderland, robert,$12.40,100,200
How do I do the line below? Assuming 100-3 =97, 100+3 = 103 after user entered 3 copies sold.
New Book Info: Alice in wonderland, robert,$12.40,97,203
function process_book_sold
{
read -p "Title: " title
read -p "Author: " author
read -p "No Of Copies Sold : " cpySold
if [ -n "$title" -a -n "$author" ]; then
perl -ne 'BEGIN{ $pattern = $ARGV[0]; shift;$pattern1 = $ARGV[0]; shift; $n=0 }
#a=split /:/;
if ($a[0] =~ m/$pattern/i and $a[1] =~ m/$pattern1/i)
{
print "Current Book Info: \n";
print "$a[0], $a[1],\$$a[2],$a[3],$a[4]\n";
}
END{ print "\n" }' "$title" "$author" /home/student/Downloads/BookDB.txt
fi
}
I don't like the mix of shell and Perl in the code, but that's apparently for pedagogical reasons so we have to ignore it.
process_book_sold()
{
read -p "Title: " title
read -p "Author: " author
read -p "No Of Copies Sold : " cpySold
if [ -n "$title" -a -n "$author" ]; then
perl -ne '
BEGIN{ $title = shift; $author = shift; $sales = shift; }
#a = split /:/;
if ($a[0] =~ m/$title/i and $a[1] =~ m/$author/i)
{
print "Current Book Info:\n";
print "$a[0], $a[1], $a[2], $a[3], $a[4]\n";
$a[3] -= $sales;
$a[4] += $sales;
print "New Book Info:\n";
print "$a[0], $a[1], $a[2], $a[3], $a[4]\n";
}
END{ print "\n" }' "$title" "$author" "$cpySold" /home/student/Downloads/BookDB.txt
fi
}
Apart from renaming pattern to title and pattern1 to author, this code passes the shell variable $cpySold to the Perl. It also uses a simpler method of retrieving the first three arguments (simply capture the value from shift). The split is the same as before. It isn't entirely clear what the format in the data file is since the printed formats use commas rather than colons to separate the fields.
I simply want the values from new book info to replace current book info in the BookDB.txt file.
I'm not convinced this is doing you any favours (you won't learn much unless you try doing it yourself), but ...
process_book_sold()
{
title="$1"
author="$2"
cpySold="$3"
if [ -n "$title" -a -n "$author" ]
then
perl -i -we '
use strict;
use English "-no_match_vars";
my $title = shift;
my $author = shift;
my $sales = shift;
while (<>)
{
chomp;
my #a = split /:/;
print STDERR "Debug: #a\n";
if ($a[0] =~ m/$title/i and $a[1] =~ m/$author/i)
{
print STDERR "Current Book Info:\n";
print STDERR "$a[0], $a[1], $a[2], $a[3], $a[4]\n";
$a[3] -= $sales;
$a[4] += $sales;
print STDERR "New Book Info:\n";
print STDERR "$a[0], $a[1], $a[2], $a[3], $a[4]\n";
$OFS = ":";
$ORS = "\n";
print #a;
}
}
' "$title" "$author" "$cpySold" BookDB.txt # /home/student/Downloads/BookDB.txt
fi
}
# read -p "Title: " title
# read -p "Author: " author
# read -p "No Of Copies Sold : " cpySold
process_book_sold "Alice in Wonderland" "Carroll" "3"
This doesn't pester me with typing the title, author or number of copies sold. You can reinstate those lines if you wish, but the function is probably more useful if it takes the arguments. (It is often good to separate user interaction from code that operates on files.) I've used the correct author name (unless you want to use Dodgson as the real name of the author who used the pseudonym Lewis Carroll). The Perl script uses the -i option to overwrite the input files. It uses the English module so it can set $OFS and $ORS. It writes debug information to STDERR (otherwise, it would be part of the information written to the file).
When the file was called pbs2.sh, a sample run of the script looked like:
$ cat BookDB.txt; bash pbs2.sh; cat BookDB.txt
Alice in Wonderland:Carroll:$12.40:74:226
Debug: Alice in Wonderland Carroll $12.40 74 226
Current Book Info:
Alice in Wonderland, Carroll, $12.40, 74, 226
New Book Info:
Alice in Wonderland, Carroll, $12.40, 71, 229
Alice in Wonderland:Carroll:$12.40:71:229
$
Clearly, this wasn't the first time I'd run the script, and at times I used values other than 3 for the number of copies sold.
With explicit file management, you can write:
process_book_sold()
{
title="$1"
author="$2"
cpySold="$3"
if [ -n "$title" -a -n "$author" ]; then
perl -we '
use strict;
use English "-no_match_vars";
my $title = shift;
my $author = shift;
my $sales = shift;
my $file = shift;
open my $fh, "+<", $file or die "Failed to open file $file for reading and writing";
my $text;
{
local $/;
$text = <$fh>;
}
chomp $text;
my #a = split /:/, $text;
print "Debug: #a\n";
if ($a[0] =~ m/$title/i and $a[1] =~ m/$author/i)
{
print "Current Book Info:\n";
print "$a[0], $a[1], $a[2], $a[3], $a[4]\n";
$a[3] -= $sales;
$a[4] += $sales;
print "New Book Info:\n";
print "$a[0], $a[1], $a[2], $a[3], $a[4]\n";
seek $fh, 0, 0;
truncate $fh, 0;
$OFS = ":";
$ORS = "\n";
print $fh #a;
}
close $fh;
' "$title" "$author" "$cpySold" BookDB.txt # /home/student/Downloads/BookDB.txt
fi
}
# read -p "Title: " title
# read -p "Author: " author
# read -p "No Of Copies Sold : " cpySold
process_book_sold "Alice in Wonderland" "Carroll" "7"
Sample run:
$ cat BookDB.txt; bash pbs1.sh; cat BookDB.txt
Alice in Wonderland:Carroll:$12.40:50:250
Debug: Alice in Wonderland Carroll $12.40 50 250
Current Book Info:
Alice in Wonderland, Carroll, $12.40, 50, 250
New Book Info:
Alice in Wonderland, Carroll, $12.40, 43, 257
Alice in Wonderland:Carroll:$12.40:43:257
$