I am generating simple SVG diagrams by using sed find and replace on some text input. However, I need a more sophisticated find and replace operation involving simple math, to alter certain X and Y values.
E.g. I need to multiply all Y values by a factor of 0.5 or 0.2 or 0.
Because of how I want this to work, it cannot be achieved with a transform operation within the SVG.*
I just need to be able to find, say, all instances of ([0-9.]*)VERT (in sed speak) and replace with the mathematical result of \1 multiplied by the constant I choose.
You could write a simple perl script. Not sure what determines $factor, but this should at least give you a running start.
#! /usr/bin/perl
my $factor=0.5;
while(my $line=<>) {
if ($line =~ /([0-9.]*)VERT/) {
my $num = $factor * $1;
$line =~ s/([0-9.]*)VERT/${num}VERT/;
}
print $line;
}
Usage: ./scriptname.pl <file_to_process.txt;
perl -ape 's/[0-9.]*(?=VERT)/$& * .5/e' file
hope this works +
Related
I am trying to extract AAA and BBB from the output of the command "dspmq".
$dspmq <- this command gives output as -->
QMNAME(AAA) STATUS(Running)
QMNAME(BBB) STATUS(Running)
But it doesn't work with the below code.
perl -e 'use Data::Dumper qw(Dumper);my #qmgrlist = `dspmq`;$size = #qmgrlist;foreach my $i (#qmgrlist){my #temp1 = split /QMNAME\(/, $i;print #temp1;}'
AAA) STATUS(Running)
BBB) STATUS(Running)
I am able to truncate "QMNAME(" but unable to truncate those to the right of AAA and BBB. Basically I want to get the string between "QMNAME(" and the immediate ")". Please assist.
I think a regex approach is better than split() here, but you could use split() by splitting on parentheses and taking the second item in the returned list.
for (#qmgrlist) {
say +(split /[()]/)[0];
}
And a brief note on your use of command-line options to run this code. You can make it simpler if you a) pipe the output of qspmq into your code and b) use -n to process a record at a time.
$ perl -nE 'say +(split /[()]/)[1]' `dspmq`
There's also -M to load modules (e.g. -MData::Dumper), but you don't seem to be using Data::Dumper any more.
split isn't going to do what you need. I would just use a regular expression to match the sub-string you need
So change the loop from this
foreach my $i (#qmgrlist)
{
my #temp1 = split /QMNAME\(/, $i;
print #temp1;
}
to this
foreach my $i (#qmgrlist)
{
print "$1\n"
if /QMNAME\((.+?)\)/;
}
Try this perl one-liner:
dspmq | perl -lne 'print for m{ QMNAME [(] ( [^)]* ) [)] }x'
Here, dspmq STDOUT is fed using a pipe | into STDIN of the perl code, which has these flags:
-e tells Perl interpreter to look for the code inline rather than in a separate script file.
-n feeds the input line by line to the inline code (this way you do not need to store the output in an array - this matters for large outputs, not in your case).
-l strips the input record separator (newline on *NIX) before feeding it to the code, and appends it automatically after during print.
The print ... for ... m{... (...) ...} code prints every pattern captured in parentheses.
The captured pattern is [^)]*, which is maximum number (0 or more) chars that are not (^) listed in the character class, that is, that are not closing parens.
[(] ... [)] are literal parentheses escaped as character classes for readability. I prefer this to escaping like so: \( ... \).
QMNAME is used to make the programmer's intentions clear: you want the string that follows QMNAME in parens. I prefer this to using the field index, such as 1, which protects you against minor variation in output of your command used with different options, on different systems, etc.
Finally, the x regex modifier in m{...}x enables comments and whitespace to be ignored, and is preferred for readability.
RELATED:
Cutting the output of a dspmq command
Desired output can be achieved with following code
use strict;
use warnings;
use feature 'say';
map{ say $1 if /QMNAME\((.+?)\)/ } <DATA>;
__DATA__
QMNAME(AAA) STATUS(Running)
QMNAME(BBB) STATUS(Running)
output
AAA
BBB
and one liner (not tested - I am on Windows computer)
dspmq | perl -lne 'print $1 if /QMNAME\((.+?)\)/'
This question already has answers here:
Shell command to sum integers, one per line?
(45 answers)
Closed 5 years ago.
I'm currently trying to write a function in my bash script that does the following: Take in a file as an argument and calculate the sum of the numbers within that file. I must make use of a for loop and the bc command.
Example of values in the file (each value on their own line):
12
4
53
19
6
So here's what I have so far:
function sum_values() {
for line in $1; do
#not sure how to sum the values using bc
done
}
Am I on the right track? I'm not sure how to implement the bc command in this situation.
You can do it easily without the need of a for loop.
paste -s -d+ numbers.txt | bc
You are not on track. Why?
You are passing the whole file content as a variable which requires to store the whole file in memory. Not a problem with a 1, 2, 3 example, big no go in real life.
You are iterating over the content of a file using a for in loop assuming that you are iterating over the lines of that file. That is not true, because word splitting will be performed which makes the for in loop literally iterate over words, not lines.
As others mentioned, the shell is not the right tool for it. That's because such kind of processing is very slow with the shell compared to awk for example. Furthermore the shell is not able to perform floating point operations, meaning you can only process integers. Use awk.
Correct would be (with bash, for educational purposes):
# Expects a filename
sum() {
filename=${1}
s=0
while read -r line ; do
# Arithmetic expansion
s=$((s+line))
# Or with bc
# s=$(bc <<< "${s}+${line}")
# With floating point support
# s=$(bc -l <<< "${s}+${line}")
done < "${filename}"
echo "${s}"
}
sum filename
With awk:
awk '{s+=$0}END{print s}' filename
While awk (or other higher level language: perl, python, etc) would be better suited for this task, you are on the right track for doing it the naive way. Tip:
$ x=1
$ y=$(bc <<<"$x + 1")
$ echo $y
2
To do math in bash we surround an operation in $(( ... ))
Here are some examples:
$(( 5 + 5 )) # 10
my_var = $((5 + 5)) # my_var is now 10
my_var = $(($my_var + 5)) # my_var is now 10
Solution to your problem:
function sum_values() {
sum=0
for i in $(<$1); do
sum=$(($sum + $i))
done
echo $sum
}
Note that you could have also done $(cat $1) instead of $(<$1) in the solution above.
Edit: Replaced return $sum with echo $sum
I realize the question name doesn't really tell much but I didn't really know how to explain it shortly, so here's the long version.
First, here's my current code:
#! /usr/bin/perl
use strict;
use warnings;
my $input;
while (<>) {
$input .= $_;
}
$input =~ s/ |\n//g;
print "\n";
What I want to do is make a calculator, e.g. when a user does echo "8 * 5 + 21-15" | calculate it will calculate it correctly. So here's my thought progress. First I take the string as a whole and strip it of all whitespace characters. Then I wanted to index() it for the occurence of *, +, / or -. Then I wanted to add all the characters before any of those operators to a string and then (int) the string and then do the same to the part after the operator and then do the operation between them. But I don't actually have much of a clue on how to do this. Also, I'm very new to Perl (3 days experience) so please go slowly on me if possible.
Thanks a lot.
If you can accept that your calculator won't be able to handle parenthesis, use a regular expression to parse the string for you:
#!/usr/bin/env perl
use strict;
use warnings;
my #tokens = <STDIN> =~ /(\d+|\+|-|\*|\/)/g;
print "$_\n" for #tokens;
this will provide you with an array of tokens that you can work on, so
echo "8 * 5 + 21-15" | script.pl
will print
8
*
5
+
21
-
15
Now it's up to you to write some code that does the right calculations on the tokens. It isn't too hard if you don't try parsing parens, but if you do, you'll need to write a recursive parser, which is much harder.
I want to remove n characters from each line using PERL.
For example, I have the following input:
catbathatxx (length 11; 11%3=2 characters) (Remove 2 characters from this line)
mansunsonx (length 10; 10%3=1 character) (Remove 1 character from this line)
#!/usr/bin/perl -w
open FH, "input.txt";
#array=<FH>;
foreach $tmp(#array)
{
$b=length($tmp)%3;
my $c=substr($tmp, 0, length($tmp)-$b);
print "$c\n";
}
I want to output the final string (after the characters have been removed).
However, this program is not giving the correct result. Can you please guide me on what the mistake is?
Thanks a lot. Please let me know if there are any doubts/clarifications.
I am assuming trailing whitespace is not significant.
#!/usr/bin/env perl
use strict; use warnings;
use constant MULTIPLE_OF => 3;
while (my $line = <DATA>) {
$line =~ s/\s+\z//;
next unless my $length = length $line;
my $chars_to_remove = $length % MULTIPLE_OF;
$line =~ s/.{$chars_to_remove}\z//;
print $line, "\n";
}
__DATA__
catbathatxx
mansunsonx
0123456789
012345678
The \K regex sequence makes this a lot clearer; it was introduced in Perl v5.10.0.
The code looks like this
use 5.10.0;
use warnings;
for (qw/ catbathatxx mansunsonx /) {
(my $s = $_) =~ s/^ (?:...)* \K .* //x;
say $s;
}
output
catbathat
mansunson
In general you would want to post the result you are getting. That being said...
Each line in the file has a \n (or \r\n on windows) on the end of it that you're not accounting for. You need to chomp() the line.
Edit to add: My perl is getting rusty from non-use but if memory serves me correct you can actually chomp() the entire array after reading the file: chomp(#array)
You should use chomp() on your array, like this:
#array=<FH>;
chomp(#array);
perl -plwe 'chomp; $c = length($_) % 3; chop while $c--' < /tmp/zock.txt
Look up the options in perlrun. Note that line endings are characters, too. Get them out of the way using chomp; re-add them on output using the -l option. Use chop to efficiently remove characters from the end of a string.
Reading your code, you are trying to print just the first 'nx3' characters for the largest value of n for each line.
The following code does this using a simple regular expression.
For each line, it first removes the line ending, then greedy matches
as many .{3} as it can (. matches any character, {3} asks for exactly 3 of them).
The memory requirement of this approach (compared with using an array the size of your file) is fixed. Not too important if your file is small compared with your free memory, but sometimes files are gigabytes, and sometimes memory is very small.
It's always worth using variable names that reflect the purpose of the variable, rather than things like $a or #array. In this case I used only one variable, which I called $line.
It's also good practice to close files as soon as you have finished with them.
#!/usr/bin/perl
use strict;
use warnings; # This will apply warnings even if you use command perl to run it
open FH, '<', 'input.txt'; # Use three part file open - single quote where no interpolation required.
for my $line (<FH>){
chomp($line);
$line =~ s/((.{3})*).*/$1\n/;
print $line;
}
close FH;
I have a string which holds a decimal value in it and I need to convert that string into a floating point variable. So an example of the string I have is "5.45" and I want a floating point equivalent so I can add .1 to it. I have searched around the internet, but I only see how to convert a string to an integer.
You don't need to convert it at all:
% perl -e 'print "5.45" + 0.1;'
5.55
This is a simple solution:
Example 1
my $var1 = "123abc";
print $var1 + 0;
Result
123
Example 2
my $var2 = "abc123";
print $var2 + 0;
Result
0
Perl is a context-based language. It doesn't do its work according to the data you give it. Instead, it figures out how to treat the data based on the operators you use and the context in which you use them. If you do numbers sorts of things, you get numbers:
# numeric addition with strings:
my $sum = '5.45' + '0.01'; # 5.46
If you do strings sorts of things, you get strings:
# string replication with numbers:
my $string = ( 45/2 ) x 4; # "22.522.522.522.5"
Perl mostly figures out what to do and it's mostly right. Another way of saying the same thing is that Perl cares more about the verbs than it does the nouns.
Are you trying to do something and it isn't working?
Google lead me here while searching on the same question phill asked (sorting floats) so I figured it would be worth posting the answer despite the thread being kind of old. I'm new to perl and am still getting my head wrapped around it but brian d foy's statement "Perl cares more about the verbs than it does the nouns." above really hits the nail on the head. You don't need to convert the strings to floats before applying the sort. You need to tell the sort to sort the values as numbers and not strings.
i.e.
my #foo = ('1.2', '3.4', '2.1', '4.6');
my #foo_sort = sort {$a <=> $b} #foo;
See http://perldoc.perl.org/functions/sort.html for more details on sort
As I understand it int() is not intended as a 'cast' function for designating data type it's simply being (ab)used here to define the context as an arithmetic one. I've (ab)used (0+$val) in the past to ensure that $val is treated as a number.
$var += 0
probably what you want. Be warned however, if $var is string could not be converted to numeric, you'll get the error, and $var will be reset to 0:
my $var = 'abc123';
print "var = $var\n";
$var += 0;
print "var = $var\n";
logs
var = abc123
Argument "abc123" isn't numeric in addition (+) at test.pl line 7.
var = 0
Perl really only has three types: scalars, arrays, and hashes. And even that distinction is arguable. ;) The way each variable is treated depends on what you do with it:
% perl -e "print 5.4 . 3.4;"
5.43.4
% perl -e "print '5.4' + '3.4';"
8.8
In comparisons it makes a difference if a scalar is a number of a string. And it is not always decidable. I can report a case where perl retrieved a float in "scientific" notation and used that same a few lines below in a comparison:
use strict;
....
next unless $line =~ /and your result is:\s*(.*)/;
my $val = $1;
if ($val < 0.001) {
print "this is small\n";
}
And here $val was not interpreted as numeric for e.g. "2e-77" retrieved from $line. Adding 0 (or 0.0 for good ole C programmers) helped.
Perl is weakly typed and context based. Many scalars can be treated both as strings and numbers, depending on the operators you use.
$a = 7*6; $b = 7x6; print "$a $b\n";
You get 42 777777.
There is a subtle difference, however. When you read numeric data from a text file into a data structure, and then view it with Data::Dumper, you'll notice that your numbers are quoted. Perl treats them internally as strings.
Read:$my_hash{$1} = $2 if /(.+)=(.+)\n/;.
Dump:'foo' => '42'
If you want unquoted numbers in the dump:
Read:$my_hash{$1} = $2+0 if /(.+)=(.+)\n/;.
Dump:'foo' => 42
After $2+0 Perl notices that you've treated $2 as a number, because you used a numeric operator.
I noticed this whilst trying to compare two hashes with Data::Dumper.