awk --bignum messes with floating point computations - awk

Example input
42 -0.400000000000000022
I want to add 9'000'000'000'000'000'000 to the 1st column, and add 30 to the 2nd column.
$ echo 42 -0.400000000000000022 | awk '{ $1 += 9000000000000000000; $2 += 30 } { print }'
9000000000000000000 29.6
Computation for the 1st column is wrong, but the 2nd column is OK.
From the documentation and the man page, there's a --bignum option which should help me for the big integer computation.
$ echo 42 -0.400000000000000022 | awk --bignum '{ $1 += 9000000000000000000; $2 += 30 } { print }'
9000000000000000042 30
Now the 1st column is OK, but the 2nd one isn't!
Here's my AWK version, running on Ubuntu 16.04:
$ awk -V
GNU Awk 4.1.3, API: 1.1 (GNU MPFR 3.1.4, GNU MP 6.1.0)
Copyright (C) 1989, 1991-2015 Free Software Foundation.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
What's even weirder is that I tested this inside an Ubuntu 16.04 docker container, and the output is correct for both column when using --bignum.
I actually don't know what to look for to fix this.

I also recommend this syntax with big numbers.
Use LC_ALL=C:
$ echo 42 -0.400000000000000022 | LC_ALL=C awk --bignum '{ $1 += 9000000000000000000; $2 += 30 } { print }'
9000000000000000042 29.6
Successfully tested with GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.1.0-p13, GNU MP 6.2.0)

Related

can't print anything after last field using awk [duplicate]

This question already has answers here:
Why does my tool output overwrite itself and how do I fix it?
(3 answers)
Closed 5 months ago.
I'm having trouble doing something very simple with awk. I'd like to print the last field, followed by another field.
Input file looks like this:
03 Oct 22, Southern ,Mad,WIN,Gro,,33.10
03 Oct 22, Mpd ,Mad,WIN,Auto,-208.56,
23 Sep 22, Thank ,n/a,WIN,,-97.93,
This way round works fine:
$ awk -F',' '{print "first " $6 " and then " $7}' input.csv
first and then 33.10
first -208.56 and then
first -97.93 and then
But when I swap the fields over I get the strangest result:
$ awk -F',' '{print "first " $7 " and then " $6}' input.csv
and then 0
and then -208.56
and then -97.93
I must be missing something really simple. What on earth is going on?
$ awk --version
GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)
Only suggestion I have is to update awk. It works perfectly fine on my MacBook - with this version:
awk --version
GNU Awk 5.1.1, API: 3.1 (GNU MPFR 4.1.0, GNU MP 6.2.1)

Using awk to set first character to lowercase UNIX

I've written the following bash script that utilizes awk, the aim is to set the first character to lower case. The script works mostly fine, however I'm adding an extra space when I concat the two values. Any ideas how to remove this errant space?
Script:
#!/bin/bash
foo="MyCamelCaseValue"
awk '{s=tolower(substr($1,1,1))}{g=substr($1,2,length($1))}{print s,g}' <<<$foo
Output:
m yCamelCaseValue
edit:
Please see discussion from Bobdylan and RavinderSingh13 on accepted answer as it highlights issues with default MacOs bash version.
bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin19)
Copyright (C) 2007 Free Software Foundation, Inc.
You were close and good try, you need not get length of line here, substr is intelligent enough to get the rest of the length of you mention a character position and don't give till where it should print(length value). Could you please try following.
(Usually these kind of problems could be solved by bash itself but when OP tried bash solution provided by #bob dylan its having issues because of OLD version of BASH, hence I am undeleting this one which is working for OP)
echo "$foo" | awk '{print tolower(substr($0,1,1)) substr($0,2)}' Input_file
Explanation:
Use substr function of awk to get sub-strings in current line.
Then grab the very first letter by substr($0,1,1) and wrap it inside tolower to make it in small case.
Now print rest of the line(since first character is already being captures by previous substr) by doing `substr($0,2) this will print from 2nd character to last of line.
EDIT by #bob dylan:
https://www.shell-tips.com/mac/upgrade-bash/
MacOS comes with an older version of bash. However if you're on 4+ you should be able to use the native bash function to translate the first character from upper to lower:
$ bash --version
GNU bash, version 4.4.19(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ cat foo.sh
#!/bin/bash
foo="MyCamelCaseValue"
echo "${foo,}"
$ bash foo.sh
myCamelCaseValue
Further examples for the whole string, to lower, to upper etc.
$ echo $foo
myCamelCaseValue
echo "${foo,}"
myCamelCaseValue
$ echo "${foo,,}"
mycamelcasevalue
$ echo "${foo^}"
MyCamelCaseValue
$ echo "${foo^^}"
MYCAMELCASEVALUE

Removing entire line based on a condition

I have a folder that contains 300 files. I would like to remove lines from the files if $2<=25. How can I remove the lines directly from the files(in place editing)? My code is shown below.
awk '{ for (i=1; i<=NF; i++) { if ($2 <= 25) next } print }' < *
ads 54.5 18 15.3 39.2
bdy 18.5 21 1.5 17.0
cst 36.8 22 27.7 9.1
hst 40.2 25 16.2 24.0
ads 18.9 41 5.0 13.2
bdy 20.5 45 67.0 19.0
You're script is doing too much. The cleanest solution, to my mind, inverts the condition:
awk '{ if ($2 > 25) print }'
or even:
awk '$2 > 25'
If you don't want to invert the condition, then:
awk '{ if ($2 <= 25) next; print }'
There's no need to iterate over all the fields.
Not even GNU awk supports 'in situ' file modification. You have to write the result to a temporary file, and then copy or move the temporary back over the original. (Copy preserves hard links and permissions; move breaks links and can modify owner and permissions. You need to decide whether that's a concern.)
Thanks to Ed Morton for pointing out that GNU Awk 4.x does have a mechanism to edit files 'in situ', in part because he campaigned to get it added.
The command line help won't tell you that GNU Awk 4.x supports in-place modification of files, but if you find the right part of the manual (Extension sample: inplace — which is mis-titled from my perspective; it isn't just a sample because it is a distributed extension) then you can find out that there is an extension that makes GNU Awk overwrite regular files specified on its command line.
gawk -i inplace '{ if ($2 > 25) print }' file1 …
or even:
gawk -i inplace '$2 > 25' file1 …
Note that experimentation shows that it is quite happy to modify read-only files in situ. This is consistent with sed (both GNU and BSD (Mac OS X) sub-species); they also modify read-only files in situ without warning — and preserve the permissions on the file but break any hard links.
Your script uses awk '…' < *; that is a peculiar way of ignoring the first file in your directory unless it is the only file in the directory (it is used for standard input, but if there's more than one file in the directory, standard input is ignored). You need to use just *, not < *.

Strange behavior of awk when converting hexa to decimal

Can someone explain why 2 different hexa are converted to the same decimal?
$ echo A0000044956EA2 | gawk '{print strtonum("0x" $1)}'
45035997424348832
$ echo A0000044956EA0 | gawk '{print strtonum("0x" $1)}'
45035997424348832
Starting with GNU awk 4.1 you can use --bignum or -M
$ awk -M 'BEGIN {print 0xA0000044956EA2}'
45035997424348834
$ awk -M 'BEGIN {print 0xA0000044956EA0}'
45035997424348832
§ Command-Line Options
Not as much an answer but a workaround to at least not bin the strtonum function completely:
It seems to be the doubles indeed. I found the calculation here : strtonum.
Nothing wrong with it.
However if you really need this in some awk you should strip the last digit from the hexa number and manually add that after the strtonum did its calculation on the main part of it.
So 0xA0000044956EA1 , 0xA0000044956EA2 and 0xA0000044956EA"whatever" should all become 0xA0000044956EA0 with a simple regex and then add the "whatever".
Edit* Maybe I should delete this all together as I am even to downgrade this even further. This is not working to satisfaction either, just tried it and I actually can't add a number that small to a number this big i.e. print (45035997424348832 + 4) just comes out as 45035997424348832. So this workaround will have to remain having output like 45035997424348832 + 4 for hexa 0xA0000044956EA4.

How to read two files and add or subtract balance in field from another file field

File 1:
Joes Garage
Utility Muffin Research Kitchen
Apple
File 2:
Joes Garage $100.24 payment
PipCo $20.13 due
Utility Muffin Research Kitchen $2.44 due
Uber $50.33 payment
Microsoft $120.33 due
Apple $220.33 payment
uber $40.44 payment
PipCo $40.99 payment
Apple $100.44 due
I want only print a read from business name listed in file 1 and calculate their balance.
for example, a business may have different transactions. therefore, print that business with only one balance.
Thanks
Disclaimer: I am not sure if I interpret the question correctly.
Here is a version that cheats and uses sed to pre-format file2 so we can directly access the relevant fields in awk. If you want to do all the work just using awk you will probably need to use some sort of loop and/or split.
$ cat t.awk
#!/usr/bin/awk -f
BEGIN { FS=":" }
NR==FNR {
switch($3) {
case "payment":
balance[$1] += $2
break
case "due":
balance[$1] -= $2
break
}
next
}
{
if ($0 in balance)
printf "%s %s$%0.2f\n",
$0,
balance[$0] >= 0 ? "" : "-",
sqrt(balance[$0]*balance[$0])
}
.
$ ./t.awk <(sed -e 's# \$#:#' -e 's#[[:digit:]] #:#' file2.txt) file1.txt
Joes Garage $100.20
Utility Muffin Research Kitchen -$2.40
Apple $119.90
If this is homework which requires you to only use awk this should be able to serve as a starting point without doing all the work for you.