Unix & Linux Asked by Luigi Tiburzi on January 5, 2021
I am trying to perform a mathematical operation with sed
, but it continues to treat my variables as strings. The input is of this kind:
$ echo 12 | sed 's/[0-9]*/&+3/'
$ 12+3
I’d like to have 15 as output. I need to do the operation and replace its mathematical result in only one passage, because I am running the program as a Python daemon, and I want to avoid passages like redirecting stdout
on files, open those files, perform operations, extract the result, do the replacement. To me, sed
seems the best to perform all in one line.
I’ve tried to cast both input and output in various ways like
$ echo 12 | sed 's/[0-9]*/int(&+3)/'
$ echo 12 | sed 's/[0-9]*/int(&+3)/'
$ echo 12 | sed 's/[0-9]*/int(&+3)/'
but the result was always a printing of the second field.
If you honestly want to use sed, then this is the way to go:
s/[0-9]/<&/g
s/0//g; s/1/|/g; s/2/||/g; s/3/|||/g; s/4/||||/g; s/5/|||||/g; s/6/||||||/g
s/7/|||||||/g; s/8/||||||||/g; s/9/|||||||||/g
: tens
s/|</<||||||||||/g
t tens
s/<//g
s/+//g
: minus
s/|-|/-/g
t minus
s/-$//
: back
s/||||||||||/</g
s/<([0-9]*)$/<01/
s/|||||||||/9/; s/||||||||/8/; s/|||||||/7/; s/||||||/6/; s/|||||/5/; s/||||/4/
s/|||/3/; s/||/2/; s/|/1/
s/</|/g
t back
Input:
1+2
100+250
100-250
Output:
3
350
-150
Your mission, should you choose to accept it, is to implement multiplication.
Correct answer by Simon Richter on January 5, 2021
Not sure if the 'e' flag for the 's' command has been discussed yet but here's how I'd solve this problem
$ echo 12 | sed -E 's/[0-9]*/expr & + 3/e'
15
the 'e' flag takes the current pattern line and runs it as a command before replacing the pattern space with the output. In this case using the 'expr' command to do our math. source: The S Command
A little more complicated, but converts multiple digits that end in a space from bytes->kilobytes (avoids numbers with a percent sign)
$ df | sed -E '1!{s/(b)([0-9]+)(s)/1$(expr 2 / 1024)kb3/g};s/.*/printf "%s" "&"/e'
Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on
/dev/disk1s5 478144kb 21563kb 141089kb 14% 477kb 2390246kb 0% /
devfs 0kb 0kb 0kb 100% 0kb 0kb 100% /dev
/dev/disk1s1 478144kb 297544kb 141089kb 68% 1727kb 2388995kb 0% /System/Volumes/Data
/dev/disk1s4 478144kb 16384kb 141089kb 11% 0kb 2390723kb 0% /private/var/vm
map auto_home 0kb 0kb 0kb 100% 0kb 0kb 100% /System/Volumes/Data/home
/dev/disk2s1 1188kb 987kb 201kb 84% 0kb 4194303kb 0% /Volumes/RecoveryHDMeta
/dev/disk3s1 3822kb 2528kb 1294kb 67% 46kb 4194257kb 0% /Volumes/macOS Base System
1!{s/(b)([0-9]+)(s)/1$(expr 2 / 1024)kb3/g};
;s/.*/printf "%s" "&"/e'
Answered by Matt on January 5, 2021
Although using sed expression is great it has its limitations. For example following fails:
$ echo "1000000000000000000000000000000+1" | sed -e 's/([0-9]*)+([0-9]*)/expr 1 + 2/e'
expr: 1000000000000000000000000000000: Numerical result out of range
To overcome this limitation one my simply turn to the built in power of pure sed and implement following arbitrary length decimal adder:
#!/bin/sed -f s/+/n/g s/$/nn0/ :LOOP s/^(.*)(.)n(.*)(.)n(.*)n(.)$/01n03n5n624/ h s/^.*n.*n.*n(...)$/1/ # decimal full adder module # INPUT: 3digits (Carry in, A, B,) # OUTPUT: 2bits (Carry, Sum) s/$/;000=00001=01002=02003=03004=04005=05006=06007=07008=08009=09010=01011=02012=03013=04014=05015=06016=07017=08018=09019=10020=02021=03022=04023=05024=06025=07026=08027=09028=10029=11030=03031=04032=05033=06034=07035=08036=09037=10038=11039=12040=04041=05042=06043=07044=08045=09046=10047=11048=12049=13050=05051=06052=07053=08054=09055=10056=11057=12058=13059=14060=06061=07062=08063=09064=10065=11066=12067=13068=14069=15070=07071=08072=09073=10074=11075=12076=13077=14078=15079=16080=08081=09082=10083=11084=12085=13086=14087=15088=16089=17090=09091=10092=11093=12094=13095=14096=15097=16098=17099=18100=01101=02102=03103=04104=05105=06106=07107=08108=09109=10110=02111=03112=04113=05114=06115=07116=08117=09118=10119=11120=03121=04122=05123=06124=07125=08126=09127=10128=11129=12130=04131=05132=06133=07134=08135=09136=10137=11138=12139=13140=05141=06142=07143=08144=09145=10146=11147=12148=13149=14150=06151=07152=08153=09154=10155=11156=12157=13158=14159=15160=07161=08162=09163=10164=11165=12166=13167=14168=15169=16170=08171=09172=10173=11174=12175=13176=14177=15178=16179=17180=09181=10182=11183=12184=13185=14186=15187=16188=17189=18190=10191=11192=12193=13194=14195=15196=16197=17198=18199=19/ s/^(...)[^;]*;[^;]*1=(..).*/2/ H g s/^(.*)n(.*)n(.*)n...n(.)(.)$/1n2n53n4/ /^([0]*)n([0]*)n/ { s/^.*n.*n(.*)n(.)/21/ s/^0(.*)/1/ q } b LOOP
The way it works is by implementing decimal adder module that adds two input digits (A and B) as well as Carry Bit and produces a Sum and Carry bit. The idea is borrowed from electronic where binary adder does the same for binary numbers. All we have to do is loop the adder over all digits and we can add arbitrary length numbers (limited by memory). Below is the adder in action:
./decAdder.sed
666666666666666666666666666666999999999999991111111112222+1100000000000000000000011111111111111111111111111111111111
1766666666666666666666677777778111111111111102222222223333
In exactly the same way one can implement binary (or any other base) adder. All you have to do is replace the line that starts with s/$/;000=00001...
with proper substitution pattern for given base. For example: s/$/;000=00001=01010=01011=10100=01101=10110=10111=11/
is substitution pattern for arbitrary length binary adder.
You can fit the code documented on my github.
Answered by Emsi on January 5, 2021
Here's a Perl solution:
echo 12 | perl -wlpe '$_ += 3'
# Output: 15
If you prefer to change the first set of digits encountered in a string, you can use:
echo I am 12 years old. | perl -wlpe 's/(d+)/$1 + 3/e'
# Output: I am 15 years old.
If you prefer to change all the sets of digits in a string, you can use the /g
modifier, like this:
echo They are 11, 12, and 13 years old. | perl -wlpe 's/(d+)/$1 + 3/eg'
# Output: They are 14, 15, and 16 years old.
Answered by J-L on January 5, 2021
I really don't get why the extreme complexity of the accepted answer, either of the below do what you want:
echo 12 | sed 's/[0-9]*/echo $(( & + 3 ))/e'
or
echo 12 | sed 's/[0-9]*/expr & + 3/e'
I think it might require GNU sed, but I am not sure.
Answered by michelpm on January 5, 2021
I tried to accept your challenge @Richter, this is what I did using part of your code:
sed 's/[0-9]/<&/g
s/0//g; s/1/|/g; s/2/||/g; s/3/|||/g; s/4/||||/g; s/5/|||||/g; s/6/||||||/g
s/7/|||||||/g; s/8/||||||||/g; s/9/|||||||||/g
: tens
s/|</<||||||||||/g
t tens
s/<//g
s/.**$/0/
s/^*.*/0/
s/*|/*/
: mult
s/(|*)*|/1<1*/
t mult
s/*//g
s/<//g
: back
s/||||||||||/</g
s/<([0-9]*)$/<01/
s/|||||||||/9/; s/||||||||/8/; s/|||||||/7/; s/||||||/6/; s/|||||/5/; s/||||/4/
s/|||/3/; s/||/2/; s/|/1/
s/</|/g
t back'
Input:
04*3
4*3
40*3
42*32
150*20
1*3
3*1
0*3
3*0
Output: all the correct results
Answered by Luigi Tiburzi on January 5, 2021
If you mix in some bashism:
echo $(($(echo 12 | sed 's/[0-9]*/&+3/')))
To extract the number from a text:
echo $(($(echo "foo12bar" | sed -r 's/[^0-9]*([0-9]*).*/1+3/')))
Without sed, just bash:
var="foo12bar"
echo $((${var//[^0-9]/}+3))
replaces every non-digit ${var//[^0-9]/}
and does arithmetic in double round parens: $((x+3))
Answered by user unknown on January 5, 2021
just feed the string into a calculator
echo 12 | sed 's/[0-9]*/&+3/' | bc
Answered by glenn jackman on January 5, 2021
perl
allows for a very similar construct to sed
's ... one difference is that perl
can do more complex things... sed
is very good for simple text substitions
echo 'a12' | perl -pe 's/([0-9]+)/($1+3)/e' # the trailing /e means evaluate
output
a15
Answered by Peter.O on January 5, 2021
If you definitely have to combine regular expressions and arithmetic operations, choose a language where the regular expression's replacement parameter can be a callback function.
Perl, Ruby, JavaScript and Python are such languages:
bash-4.2$ echo 12 | perl -pe 's/d+/$&+3/e'
15
bash-4.2$ echo 12 | ruby -pe '$_.sub!(/d+/){|s|s.to_i+3}'
15
bash-4.2$ echo 12 | js -e 'print(readline().replace(/d+/,function(s){return parseInt(s)+3}))'
15
bash-4.2$ echo 12 | python -c 'import re;print re.sub("d+",lambda s:str(int(s.group(0))+3),raw_input())'
15
Answered by manatwork on January 5, 2021
Another simple bash
solution, that actually works in a pipe:
echo 12 | { read num; echo $(( num + 3)); }
Answered by rozcietrzewiacz on January 5, 2021
sed
isn't the best option here, it doesn't do arithmetics natively (see Increment a number for how you could possibly do it though). You could do that with awk
:
$ echo 12 | awk '{print $0+3}'
15
The best piece of code to use will depend on the exact format of your input and what you want/need to do if it is not numeric, or contains more than one number, etc.
You could also do this only with bash
:
$ echo $(( $(echo 12) + 3 ))
or using expr
in a similar fashion.
Answered by Mat on January 5, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP