I am currently having a very simple problem with capturing the output from a backticked shell command. I apologize that the problem is rather simple.
I have some sorted array (#valid_runs) which I know contains consecutive, duplicate elements. I want to use backticks to echo this array to uniq. I want to capture the STDOUT in an array. I attempt to do so like this.
#unique_valids = `echo '#valid_runs' | uniq`;
print #unique_valids;
This print statement yields nothing. For that matter neither does this.
#unique_valids = `echo '#valid_runs'`;
print #unique_valids;
I know how to use uniq and echo. This seems rather odd to me. I would think this has more to do with perl arrays than proper use of those commands. I have searched around a bit elsewhere, so please don't pelt me with downvotes just because the solution might seem trivial. Thanks again for your time.
NOTE on solutions:
TLP's solution is the most straightforward as far handling the uniq problem. I am being rather flexible, since all responses suggested not making a system call for this problem. If Perl's uniq function the same as Unix's uniq then the array ought to remain sorted.
John Corbett's solution works well if you don't care about a sorted result.
Using system calls for something that can easily be accomplished with perl code is not a good idea. The module List::MoreUtils has a uniq function that does what you require:
use List::MoreUtils qw(uniq);
my #unique = uniq #runs;
The subroutine inside the module itself is very simple, though, exactly like theglauber's answer:
sub uniq (#) {
my %seen = ();
grep { not $seen{$_}++ } #_;
}
you should just store the array into a hash, because hash keys are always unique. You can do that like this:
my %temp_hash = map { $_ => 1 } #valid_runs;
my #unique_valids = keys %temp_hash;
that's the perl way of doing it anyway. There's no need to use back tics here (I try to avoid those as much as I can).
It's easy to do this in perl. Here's a rather obscure but fun way to dedup an array:
#dedup = grep !$seen{$_}++ #orig_array;
Figure out what this is doing by checking the documentation for the perl function grep.
If you have to use uniq, you probably need to put each array element in a separate line.
join("\n", #your_array)
should achieve that.
#!/usr/bin/perl
use warnings;
#a = (1, 2, 3, 3, 4, 4, 5);
$cmd = "/usr/bin/uniq <<EOF\n";
$cmd .= $_."\n" foreach (#a);
$cmd .= "EOF\n";
$result = `$cmd`;
print "Cmd: $cmd\n";
print "Result is $result";
#u = split /\n/,$result;
print "After ",join " ",#u,"\n";
This does what you ask, but theglauber's answer is still better Perl.
Related
I have a question about Perl more out of curiosity than necessity. I have seen there are many ways to do a lot of things in Perl, a lot of the time the syntax seems unintuitive to me (I've seen a few one liners doing som impressive stuff).
So.. I know the function split returns an array. My question is, how do I go about printing the first element of this array without saving it into a special variable? Something like $(split(" ",$_))[0] ... but one that works.
You're 99% there
$ perl -de0
Loading DB routines from perl5db.pl version 1.33
Editor support available.
Enter h or `h h' for help, or `man perldebug' for more help.
main::(-e:1): 0
DB<1> $a = "This is a test"
DB<2> $b = (split(" ",$a))[0]
DB<3> p $b
This
DB<4> p "'$b'"
'This'
This should do it:
print ((split(" ", $_))[0]);
You need one set of parentheses to allow you to apply array indexing to the result of a function. The outer parentheses are needed to get around special parsing of print arguments.
Try this out to print the first element of a whitespace separated list. The \s+ regex matches one or more whitespace characters to split on.
echo "1 2 3 4" | perl -pe 'print +(split(/\s+/, $_))[0]'
Also, see this related post.
I have a simple enough problem I think, I have recently ran a script which extracted specific information from the string in each element in an array. I have written this before and it functions well however when trying the very simple version of it right now it will not presen data only the same response uninitialized value argument! I am getting really frustrated as my previous code works. I am clearly doing something STUPID and would love some help!
#!/usr/bin/env perl
use strict;
use warnings;
my#histone;
my$line;
my$idea;
my$file="demo_site.txt";
open(IN, "<$file")||die"\ncannot be opend\n";
#histone=<IN>;
print #histone;
foreach $line(#histone)
{
$line=~ m/([a-zA-Z0-9]+)\t[0-9]+\t[0-9]+\t/;
print$1."\n";
print$2."\n";
print$3."\n";
}
The infile "demo_site.txt" takes the format of a tab delimited .txt file:
chr9 1234 5678 . 200 . 14.0 -1
This file has multiple lines as above and I wish to extract the first three items of data so the output looks as follows.
chr9
1234
5678
Cheers!
You don't really need a regular expression since it's tab delimited.
foreach $line(#histone)
{
#line_data = split(/\t/,$line)
print $line_data[0]."\n";
print $line_data[1]."\n";
print $line_data[2]."\n";
}
Edit:
If you want to assign the values to specific named variables, assign it in a temporary array.
($varA, $varB, $varC .... ) = split(/\t/,$line)
The actual problem here is that you're trying to print the values of $1, $2 and $3, but you only have one set of capturing parenthesis in your regex, so only $1 gets a value. $2 and $3 will remain undefined and hence give you that error when you try to print them.
The solution is to add two more sets of capturing parenthesis. I expect you want something like this:
$line=~ m/([a-zA-Z0-9]+)\t([0-9]+)\t([0-9]+)\t/;
Let's assume, that file.txt have what you want: (file.txt eq demo_site.txt )
chr9 1234 5678 . 200 . 14.0 -1
you can use simple thing:
perl -ane '$" = "\n"; print "#F[0..2]"' file.txt 1>output.txt
One-liners in Perl are powerful. And you don't need to write your scripts for simple tasks;)
Just open Terminal sometimes;)
P.S:
This is not very good one-liner, I know, but It do what It must.
$line=~ m/([a-zA-Z0-9]+)\t[0-9]+\t[0-9]+\t/)
First of all, the parens are not balanced.
Second, I haven't checked this, but don't you need a set of parens for each capture?
Third, as misplacedme said split() is definitely the way to go. ;)
If I may self-promote, you can use Tie::Array::CSV to give direct read-write access to the file as a Perl array of arrayrefs.
use strict;
use warnings;
use Tie::Array::CSV;
tie my #file, 'Tie::Array::CSV', 'demo_site.txt', sep_char => "\t";
print $file[0][0]; # first line before first tab
$file[2][1] = 10; # set the third line between the first and second tabs
So I'll start of by saying I'm not very familiar with Perl. I have a project that I've been handed at work that requires quite a bit of Perl work. Most of it makes sense but I'm stuck on a very simple issue.
I've simplified my code for example purposes. If I can get this to work I can code the rest of the project no problem, but for some reason I can't seem to get something as simple as the following to work for me:
#!/usr/local/bin/perl
#names = ('Harry','Larry','Moe');
foreach $name (#names){
if($name == 'Harry'){
print $name;
}
}
Any help is greatly appreciated!
Edit: fyi the output of the above is the following:
HarryLarryMoe
String comparisons in Perl aren't done with == but with eq. Perl is does not consider the integer 13 different than the string '13' until you operate on them. String values that don't represent numbers in any obvious way (e.g. 'Harry') are coerced to a numeric value of zero. Thus, $name=='Harry' will always hold, but $name eq 'Harry' won't.
Take a look at perldoc perlop for more information.
Edited to add: If you had enabled the warnings pragma, then the interpreter would have pointed this out to you. In fact, it's always a good idea to use strict and use warnings in pretty much any Perl code that you write. In particular, this code (executed as a one-liner from the command line via perl -e):
use strict;
use warnings;
my #names=("Harry","Larry","Moe");
foreach my $name(#names)
{
if($name=="Harry")
{
print "$name\n";
}
}
produces the output
Argument "Harry" isn't numeric in numeric eq (==) at -e line 7.
Argument "Harry" isn't numeric in numeric eq (==) at -e line 7.
Harry
Argument "Larry" isn't numeric in numeric eq (==) at -e line 7.
Larry
Argument "Moe" isn't numeric in numeric eq (==) at -e line 7.
Moe
It is this way because you use numerical comparison but should use string one (eq). $name and Harry evaluate both to 0, so your comparision is in your example always true.
First I should perhaps explain what I want to do...
I have 'n' amounts of files with 'n' amount of lines. All I know is
that the line count will be even.
The user selects the files that they want. This is saved into an
array called ${selected_sets[#]}.
The program will print to screen a randomly selected 'odd numbered'
line from a randomly selected file.
Once the line has been printed, I don't want it printed again...
Most of it is fine, but I am having trouble creating arrays based on the contents of ${selected_sets[#]}... I think I have got my syntax all wrong :)
for i in ${selected_sets[#]}
do
x=1
linecount=$(cat $desired_path/$i | wc -l) #get line count of every set
while [ $x -le $linecount ]
do ${i}[${#${i}[#]}]=$x
x=$(($x+2)) # only insert odd numbers up to max limit of linecount
done
done
The problem is ${i}[${#${i}[#]}]=$x
I know that I can use array[${#array[#]}]=$x but I don't know how to use a variable name.
Any ideas would be most welcome (I am really stumped)!!!
In general, this type is question is solved with eval. If you want a a variable named "foo" and have a variable bar="foo", you simply do:
eval $bar=5
Bash (or any sh) treats that as if you had typed
foo=5
So you may just need to write:
eval ${i}[\${#${i}[#]}]=$x
with suitable escapes. (A useful technique is to replace 'eval' with 'echo', run the script and examine the output and make sure it looks like what you want to be evaluated.)
You can create named variables using the declare command
declare -a name=${#${i}[#]}
I'm just not sure how you would then reference those variables, I don't have time to investigate that now.
Using an array:
declare -a myArray
for i in ${selected_sets[#]}
do
x=1
linecount=$(cat $desired_path/$i | wc -l) #get line count of every set
while [ $x -le $linecount ]
do
$myArray[${#${i}[#]}]=$x
let x=x+1 #This is a bit simpler!
done
done
Beware! I didn't test any of the above. HTH
I'm trying build an sort of property set in ksh.
Thought the easiest way to do so was using arrays but the syntax is killing me.
What I want is to
Build an arbitrary sized array in a config file with a name and a property.
Iterate for each item in that list and get that property.
I theory what I wish I could do is something like
MONITORINGSYS={
SYS1={NAME="GENERATOR" MONITORFUNC="getGeneratorStatus"}
SYS2={NAME="COOLER" MONITORFUNC="getCoolerStatus"}
}
Then later on, be able to do something like:
for CURSYS in $MONITORINGSYS
do
CSYSNAME=$CURSYS.NAME
CSYSFUNC=$CURSYS.MONITORFUNC
REPORT="$REPORT\n$CSYSNAME"
CSYSSTATUS=CSYSFUNC $(date)
REPORT="$REPORT\t$CSYSSTATUS"
done
echo $REPORT
Well, that's not real programming, but I guess you got the point..
How do I do that?
[EDIT]
I do not mean I want to use associative arrays. I only put this way to make my question more clear... I.e. It would not be a problem if the loop was something like:
for CURSYS in $MONITORINGSYS
do
CSYSNAME=${CURSYS[0]}
CSYSFUNC=${CURSYS[1]}
REPORT="$REPORT\n$CSYSNAME"
CSYSSTATUS=CSYSFUNC $(date)
REPORT="$REPORT\t$CSYSSTATUS"
done
echo $REPORT
Same applies to the config file.. I'm just looking for a syntax that makes it minimally readable.
cheers
Not exactly sure what you want... Kornshell can handle both associative and indexed arrays.
However, Kornshell arrays are one dimensional. It might be possible to use indirection to emulate a two dimensional array via the use of $() and eval. I did this a couple of times in the older Perl 4.x and Perl 3.x, but it's a pain. If you want multidimensional arrays, use Python or Perl.
The only thing is that you must declare arrays via the typedef command:
$ typeset -A foohash #foohash is an associative array
$ typeset -a foolist #foolist is an integer indexed array.
Maybe your script can look something like this
typeset -a sysname
typeset -a sysfunct
sysname[1] = "GENERATOR"
sysname[2] = "COOLER"
sysfunc[1] = "getGeneratorStatus"
sysfunc[2] = "getCoolerStatus"
for CURSYS in {1..2}
do
CSYSNAME="${sysname[$CURSYS]}"
CSYSFUNC="${sysfunc[$CURSYS]}"
REPORT="$REPORT\n$CSYSNAME"
CSYSSTATUS=$(eval "CSYSFUNC $(date)")
REPORT="$REPORT\t$CSYSSTATUS"
done
echo $REPORT
ksh93 now has compound variables which can contain a mixture of indexed and associative arrays. No need to declare it as ksh will work it out itself.
#!/bin/ksh
MONITORINGSYS=(
[SYS1]=(NAME="GENERATOR" MONITORFUNC="getGeneratorStatus")
[SYS2]=(NAME="COOLER" MONITORFUNC="getCoolerStatus")
)
echo MONITORING REPORT
echo "-----------------"
for sys in ${!MONITORINGSYS[*]}; do
echo "System: $sys"
echo "Name: ${MONITORINGSYS[$sys].NAME}"
echo "Generator: ${MONITORINGSYS[$sys].MONITORFUNC}"
echo
done
Output:
MONITORING REPORT
-----------------
System: SYS1
Name: GENERATOR
Generator: getGeneratorStatus
System: SYS2
Name: COOLER
Generator: getCoolerStatus