Is there any way to use grep to find only elements that exists in specific array?
For example :
my #IPS ={"10.20.30","12.13.14","30.40.50"};
my $cmd = `netstat -Aa | grep -c IPS[0] OR IPS[1] OR IPS[2] `
print "$cmd";
I want cmd to return the number of IPS (only those found in the array) that exists in the output of netstat command.
I know I can use " | " or condition but assume that I do not know the number of elements in array.
Your #IPS array does not contain what you think it contains. I think you probably wanted:
my #IPS = ("10.20.30","12.13.14","30.40.50");
And I'd write that as:
my #IPS = qw(10.20.30 12.13.14 30.40.50);
I know I can use " | " or condition but assume that I do not know the number of elements in array
I don't think that matters at all.
# Need quotemeta() to escape the dots
my $IP_str = join '|', map { quotemeta $_ } #IPS;
my $IP_re = qr/$IP_str/;
# Keep as much of the processing as possible in Perl-space
my #found = grep { /$IP_str/ } `netstat -Aa`;
say scalar #found;
An alternative to the regex, is to turn #IPS into a hash.
my %IP = map { $_ => 1 } #IPS;
my #found = grep { $IP{$_} } `netstat -Aa`;
say scalar #found;
Update: Actually, that last example doesn't work. You would need to extract the IP addresses from the netstat output before matching against the hash. but I've left it there in case it inspires anyone to expand it.
A quick and dirty way to do this is by utilizing the $" variable in perl:
my #IPS =("10.20.30","12.13.14","30.40.50");
local $"='|';
my $cmd = `netstat -Aa | grep '#IPS' | wc -l`
print "$cmd";
The idea is that $" controls how an array variable is appears when interpolated inside some quotes. So by saying $"='|' we cause the #IPS array to be interpolated as a concatenation of its elements separated by |. Note: you should only use this if you trust of the source of #IPS e.g. if you type it yourself in the code. If it comes from an untrusted external source, the above trick might be dangerous.
Edit: If #IPS does come from an untrusted source, you can validate it first like so
/^[\d.]+$/ or die "Invalid IP address" for #IPS;
Related
I want to delete some defined elements from a array. I already have a solution with grep and one element: #big = grep { ! /3/ } #big. In case of several elements I want to put them in an array and using foreach. #big is the array from which I want delete elements from #del:
perl -e "#big = (1,2,3,4,5); #del = (2,4);
foreach $i (#del) {#big = grep { ! /$i/ } #big; print \"#big\n\"}"
Here is the output:
1 3 4 5
1 3 5
This works fine for me. If I want to use default variable $_ it does not work:
perl -e "#big = (1,2,3,4,5); #del = (2,4);
foreach (#del) {#big = grep { ! /$_/ } #big; print \"#big\n\"}"
This gives no output. Any idea what happens?
As #MattJacob points out, there are two conflicting uses for $_:
foreach (#del) {...}
This implicitly uses $_ as the loop variable. From the foreach docs:
The foreach keyword is actually a synonym for the for keyword, so you can use either. If VAR is omitted, $_ is set to each value.
Your grep command also uses $_, always, as part of its processing. So if you had a value in $_ as part of your foreach, and it was replaced in the grep ...?
From the grep docs:
Evaluates the BLOCK or EXPR for each element of LIST (locally setting $_ to each element)
Please do check the grep docs, since there is an explicit warning about modifying a list that you are iterating over. If you are trying to iteratively shrink the list, you might consider some alternative ways to process it. (For example, could you build a single pattern for grep that would combine all the values in your #del list, and just process the #big list one time? This might be quicker, too.)
I have an array $vhdlist with contents similar to the following filenames:
UVHD-S-1-5-21-8746256374-654813465-374012747-4533.vhdx
UVHD-S-1-5-21-8746256374-654813465-374012747-6175.vhdx
UVHD-S-1-5-21-8746256374-654813465-374012747-8147.vhdx
UVHD-template.vhdx
I want to use a regex and be left with an array containing only SID portion of the filenames.
I am using the following:
$sids = foreach ($file in $vhdlist)
{
[regex]::split($file, '^UVHD-(?:([(\d)(\w)-]+)).vhdx$')
}
There are 2 problems with this: in the resulting array there are 3 blank lines for every SID; and the "template" filename matches (the resulting line in the output is just "template"). How can I get an array of SIDs as the output and not include the "template" line?
You seem to want to filter the list down to those filenames that contain an SID. Filtering is done with Where-Object (where for short); you don't need a loop.
An SID could be described as "S- and then a bunch of digits and dashes" for this simple case. That leaves us with ^UVHD-S-[\d-]*\.vhdx$ for the filename.
In combination we get:
$vhdlist | where { $_ -Match "^UVHD-S-[\d-]*\.vhdx$" }
When you don't really have an array of strings, but actually an array of files, use them directly.
dir C:\some\folder | where { $_.Name -Match "^UVHD-S-[\d-]*\.vhdx$" }
Or, possibly you can even make it as simple as:
dir C:\some\folder\UVHD-S-*.vhdx
EDIT
Extracting the SIDs from a list of strings can be thought as a combined transformation (for each element, extract the SID) and filter (remove non-matches) operation.
PowerShell's ForEach-Object cmdlet (foreach for short) works like map() in other languages. It takes every input element and returns a new value. In effect it transforms a list of input elements into output elements. Together with the -replace operator you can extract SIDs this way.
$vhdlist | foreach { $_ -replace ^(?:UVHD-(S-[\d-]*)\.vhdx|.*)$,"`$1" } | where { $_ -gt "" }
The regex back-reference for .NET languages is $1. The $ is a special character in PowerShell strings, so it needs to be escaped, except when there is no ambiguity. The backtick is the PS escape character. You can escape the $ in the regex as well, but there it's not necessary.
As a final step we use where to remove empty strings (i.e. non-matches). Doing it this way around means we only need to apply the regex once, instead of two times when filtering first and replacing second.
PowerShell operators can also work on lists directly. So the above could even be shortened:
$vhdlist -replace "^UVHD-(S-[\d-]*)\.vhdx$","`$1" | where { $_ -gt "" }
The shorter version only works on lists of actual strings or objects that produce the right thing when .ToString() is called on them.
Regex breakdown:
^ # start-of-string anchor
(?: # begin non-capturing group (either...)
UVHD- # 'UVHD-'
( # begin group 1
S-[\d-]* # 'S-' and however many digits and dashes
) # end group 1
\.vhdx # '.vhdx'
| # ...or...
.* # anything else
) # end non-capturing group
$ # end-of-string anchor
To get the index of elements in fields containing /, I use the following but get an error msg:
#ind = grep($fields[$_] =~ /\/\/]);
Search pattern not terminated
For the sake of completeness, here's how it should look like (if array in question is #fields variable):
my #slash_indexes = grep { $fields[$_] =~ m~/~ } 0..$#fields;
Demo. The code given in the question misses the second param of grep - the list that should be grepped. Considering you need to collect indexes, you need to pass indexes of the original array into grep as well, and not the array itself.
You are escaping the / that should terminate your regular expression.
Just remove the last \ from before it.
First of all, let me state that I am very new to Bash scripting. I have tried to look for solutions for my problem, but couldn't find any that worked for me.
Let's assume I want to use bash to parse a file that looks like the following:
variable1 = value1
variable2 = value2
I split the file line by line using the following code:
cat /path/to/my.file | while read line; do
echo $line
done
From the $line variable I want to create an array that I want to split using = as a delimiter, so that I will be able to get the variable names and values from the array like so:
$array[0] #variable1
$array[1] #value1
What would be the best way to do this?
Set IFS to '=' in order to split the string on the = sign in your lines, i.e.:
cat file | while IFS='=' read key value; do
${array[0]}="$key"
${array[1]}="$value"
done
You may also be able to use the -a argument to specify an array to write into, i.e.:
cat file | while IFS='=' read -a array; do
...
done
bash version depending.
Old completely wrong answer for posterity:
Add the argument -d = to your read statement. Then you can do:
cat file | while read -d = key value; do
$array[0]="$key"
$array[1]="$value"
done
while IFS='=' read -r k v; do
: # do something with $k and $v
done < file
IFS is the 'inner field separator', which tells bash to split the line on an '=' sign.
I have a simple question I was hoping you guys can help shed light on. I'm steadily learning perl.
Say I have a very large string, for example take the output of:
our $z = `du -B MB /home`
This will produce a string like the following:
1MB /home/debug/Music
1MB /home/debug/Downloads
20MB /home/debug
20MB /home/
What I would like to know is, how do I go about loading this string into an array with two columns, and n rows (where n is the number of lines in the du output)?
I was trying something like the following:
my $z1 = `du -B MB /home | tail -4`;
my #c0 = split (/n/, $z1);
my $z2 = join (/\t/, #c0);
my #c2=split(/\t/, $z2);
print #c2;
Which produces the following output:
1MB/home/debug/Music1MB/home/debug/Downloads20MB/home/debug20MB/home
I suppose I can use the substitution function s///g to substitue the directories for null values, and set the SPACE values to one array, and null the space values and set that to a second array, and can set 1 array as keys to the other.
Does anyone have any suggestions as to the best way to approach this?
Any help is appreciated.
Thanks,
Diego
#!/usr/bin/perl;
my $z= `du -h -d 1 /home/*`;
my #array = split("\n",$z);
foreach my $ar(#array) {
my #ar = split("\t",$ar);
$ar = \#ar;
}
foreach my $entry(#array) {
print $entry->[0];
print $entry->[1];
}
You can probably try and store them in a hash as follows:
#!/usr/bin/perl
use strict;
use warnings;
my $data = '1MB /home/work 4MB /user/bin';
my %values = split(' ', $data);
foreach my $k (keys %values) {
print "$k: $values{$k}\n";
}
exit 0;
Note that ' ' as the first argument of split will match any whitespace character (so we make the most of it). The output for the above should be something like:
1MB: /home/work
4MB: /user/bin
You will have to work the original data into $data and check if a hash works for you.
I'm sure you perl veterans won't like this solution much, but I basically resolved to my nix roots. I was having trouble with this earlier in a for loop, but I realized I can utilize pipes to be evaluated via the shell, so long as the answer is stored in a string.
my $z1; my $z2;
$z1 = `du -B MB /home | cut -f1`
$z2 = `du -B MB /home | cut -f2`
my #a1; my #a2;
#a1 = split("\n", $z1);
#a2 = split("\n", $z2);
Array #a1 holds values from first string, #a2 holds values from 2nd string.
My question to you guys is, is there a perl equivelent to the unix 'cut' utility? I.e. where I can split a strings output to the first tab-delimited field? This is a problem I'll have to explore.
Diego
I'm honestly not sure what you're trying to accomplish. If you simply want to have an array with n elements, each of which is a string with two columns in it, you need look no farther than
my #z1 = `du -B MB /home | tail -4`;
For example, the third line of your file could be accessed (remember perl arrays are 0-based):
print $z1[2];
producing output
20MB /home/debug
More useful would be to capture each directory's size in a hash:
my %dir2size;
foreach (#z1) {
my #line = split /\s+/;
$dir2size{$line[1]} = $line[0];
}
foreach my $key (sort keys %dir2size) {
print "$key: $dir2size{$key}\n";
}
which produces the output
/home/: 20MB
/home/debug: 20MB
/home/debug/Downloads: 1MB
/home/debug/Music: 1MB