How to add in shell script? - bash

Assume let the value of the variable a be 42,
b=18+$a
The value of b should be 60
But I'm not getting the value 60. Instead it's printing 18+42.
How can i do it ?
New Query :
grep -F "$name" -A1000 filename | sed -n '1p;19p;24p'
Assume let a=10,b=20,c=30.In the above grep command can i use '$ap;$bp;$cp' instead of '1p;19p;24p' ?
Another thing, I've given as -A1000. Which implies that starting from 1p it considers till 1000 line , right ? I need to search throughout file without giving the number. Ho

Bash:
b=$((18+a))
echo "value of b is $b"
or
let b=18+a
echo "value of b is $b"

Any mathematical operation in Bash involves the use of $(())
So, for addition, you would do :-
b=$((18 + a))
Notice that the '$' before a is not required. Some more examples of mathematical operations in Bash are :-
a=$((17+1))
b=$((100-a))
c=$((a*b))
d=$((c/b))
echo $((10 * 1024 * 1024)) # Echoes the number bytes in 10 MB

Related

Random word Bash script if a number is supplied as the first command line argument then it will select from only words with that many characters

I am trying to create a Bash script that
- prints a random word
- if a number is supplied as the first command line argument then it will select from only words with that many characters.
This is my go at the first section (print a random word):
C=$(sed -n "$RANDOM p" /usr/share/dict/words)
echo $C
I am really stuck with the second section. Can anyone help?
might help someone coming from ryans tutorial
#!/bin/bash
charlen=$1
grep -E "^.{$charlen}$" $PWD/words.txt | shuf -n 1
you have to use a while loop to read every single line of that file and check if the length of a word equals the specified number ( including apostrophes ). In my o.s it is 99171 line ( i.e the file).
#!/usr/bin/env bash
readWords() {
declare -i int="$1"
(( int == 0 )) && {
printf "%s\n" "$int is 0, cant find 0 words"
return 1
}
while read getWords;do
if [[ ${#getWords} -eq $int ]];then
printf "%s\n" "$getWords"
fi
done < /usr/share/dict/words
}
readWords 20
this function takes a single argument. the declare command coerces the argument into an integer, if the argument is a string , it coerces it into a number which is 0 . Since we don't have 0 words if the specified argument ( number ) is 0 ( or a string coerced to 0 ) return from the function.
Read every single line in /usr/share/dict/words, get the length of each line with ${#getWords} ( $# >> gives the length of a string/commandline parameters/array size ) check if it equals the specified argument ( number )
A loop is not required, you can do something like
CH=$1; # how many characters the word must have
WordFile=/usr/share/dict/words; # file to read from
# find how many words that matches that length
TOTW=$(grep -Ec "^.{$CH}$" $WordFile);
# pick a random one, if you expect more than 32767 hits you
# need to do something like ($RANDOM+1)*($RANDOM+1)
RWORD=$(($RANDOM%$TOTW+1));
#show that word
grep -E "^.{$CH}$" $WordFile|sed -n "$RWORD p"
Depending on things you probably need to add checks for things like that $1 is a reasonable number, the file exist, that TOTW is >0 and so on.
This code would achieve what you want:
awk -v n="$1" 'length($0) == n' /usr/share/dict/words > /tmp/wordsHolder
shuf -n 1 /tmp/wordsHolder
Some comments: by using "$RANDOM" (as you did on your original script attempt), one would generate an integer on the range 0 - 32767, which could be more (or less) than the number of words (lines) available, given the desired number of characters on a word -- thus, potential for errors here.
To avoid that, we are using a shuf syntax that will retrieve a (sub)randomly picked word (line) on the file using its entire range (from line 1 - last line of file).

How to round a floating point number upto 3 digits after decimal point in bash

I am a new bash learner. I want to print the result of an expression given as input having 3 digits after decimal point with rounding if needed.
I can use the following code, but it does not round. Say if I give 5+50*3/20 + (19*2)/7 as input for the following code, the given output is 17.928. Actual result is 17.92857.... So, it is truncating instead of rounding. I want to round it, that means the output should be 17.929. My code:
read a
echo "scale = 3; $a" | bc -l
Equivalent C++ code can be(in main function):
float a = 5+50*3.0/20.0 + (19*2.0)/7.0;
cout<<setprecision(3)<<fixed<<a<<endl;
What about
a=`echo "5+50*3/20 + (19*2)/7" | bc -l`
a_rounded=`printf "%.3f" $a`
echo "a = $a"
echo "a_rounded = $a_rounded"
which outputs
a = 17.92857142857142857142
a_rounded = 17.929
?
You can use awk:
awk 'BEGIN{printf "%.3f\n", (5+50*3/20 + (19*2)/7)}'
17.929
%.3f output format will round up the number to 3 decimal points.
Try using this:
Here bc will provide the bash the functionality of caluculator and -l will read every single one in string and finally we are printing only three decimals at end
read num
echo $num | bc -l | xargs printf "%.3f"

Simplest method to convert file-size with suffix to bytes

Title says it all really, but I'm currently using a simple function with a case statement to convert human-readable file size strings into a size in bytes. It works well enough, but it's a bit unwieldy for porting into other code, so I'm curious to know if there are any widely available commands that a shell script could use instead?
Basically I want to take strings such as "100g" or "100gb" and convert them into bytes.
I'm currently doing the following:
to_bytes() {
value=$(echo "$1" | sed 's/[^0123456789].*$//g')
units=$(echo "$1" | sed 's/^[0123456789]*//g' | tr '[:upper:]' '[:lower:]')
case "$units" in
t|tb) let 'value *= 1024 * 1024 * 1024 * 1024' ;;
g|gb) let 'value *= 1024 * 1024 * 1024' ;;
m|mb) let 'value *= 1024 * 1024' ;;
k|kb) let 'value *= 1024' ;;
b|'') let 'value += 0' ;;
*)
value=
echo "Unsupported units '$units'" >&2
;;
esac
echo "$value"
}
It seems a bit overkill for something I would have thought was fairly common for scripts working with files; common enough that something might exist to do this more quickly.
If there are no widely available solutions (i.e - majority of unix and linux flavours) then I'd still appreciate any tips for optimising the above function as I'd like to make it smaller and easier to re-use.
See man numfmt.
# numfmt --from=iec 42 512K 10M 7G 3.5T
42
524288
10485760
7516192768
3848290697216
# numfmt --to=iec 42 524288 10485760 7516192768 3848290697216
42
512K
10M
7.0G
3.5T
toBytes() {
echo $1 | echo $((`sed 's/.*/\L\0/;s/t/Xg/;s/g/Xm/;s/m/Xk/;s/k/X/;s/b//;s/X/ *1024/g'`))
}
Here's something I wrote. It supports k, KB, and KiB. (It doesn't distinguish between powers of two and powers of ten suffixes, though, as in 1KB = 1000 bytes, 1KiB = 1024 bytes.)
#!/bin/bash
parseSize() {(
local SUFFIXES=('' K M G T P E Z Y)
local MULTIPLIER=1
shopt -s nocasematch
for SUFFIX in "${SUFFIXES[#]}"; do
local REGEX="^([0-9]+)(${SUFFIX}i?B?)?\$"
if [[ $1 =~ $REGEX ]]; then
echo $((${BASH_REMATCH[1]} * MULTIPLIER))
return 0
fi
((MULTIPLIER *= 1024))
done
echo "$0: invalid size \`$1'" >&2
return 1
)}
Notes:
Leverages bash's =~ regex operator, which stores matches in an array named BASH_REMATCH.
Notice the cleverly-hidden parentheses surrounding the function body. They're there to keep shopt -s nocasematch from leaking out of the function.
don't know if this is ok:
awk 'BEGIN{b=1;k=1024;m=k*k;g=k^3;t=k^4}
/^[0-9.]+[kgmt]?b?$/&&/[kgmtb]$/{
sub(/b$/,"")
sub(/g/,"*"g)
sub(/k/,"*"k)
sub(/m/,"*"m)
sub(/t/,"*"t)
"echo "$0"|bc"|getline r; print r; exit;}
{print "invalid input"}'
this only handles single line input. if multilines are needed, remove the exit
this checks only pattern [kgmt] and optional b. e.g. kib, mib would fail. also currently is only for lower-case.
e.g.:
kent$ echo "200kb"|awk 'BEGIN{b=1;k=1024;m=k*k;g=k^3;t=k^4}
/^[0-9.]+[kgmt]?b?$/&&/[kgmtb]$/{
sub(/b$/,"")
sub(/g/,"*"g)
sub(/k/,"*"k)
sub(/m/,"*"m)
sub(/t/,"*"t)
"echo "$0"|bc"|getline r
print r; exit
}{print "invalid input"}'
204800
Okay, so it sounds like there's nothing built-in or widely available, which is a shame, so I've had a go at reducing the size of the function and come up with something that's only really 4 lines long, though it's a pretty complicated four lines!
I'm not sure if it's suitable as an answer to my original question as it's not really what I'd call the simplest method, but I want to put it up in case anyone thinks it's a useful solution, and it does have the advantage of being really short.
#!/bin/sh
to_bytes() {
units=$(echo "$1" | sed 's/^[0123456789]*//' | tr '[:upper:]' '[:lower:]')
index=$(echo "$units" | awk '{print index ("bkmgt kbgb mbtb", $0)}')
mod=$(echo "1024^(($index-1)%5)" | bc)
[ "$mod" -gt 0 ] &&
echo $(echo "$1" | sed 's/[^0123456789].*$//g')"*$mod" | bc
}
To quickly summarise how it works, it first strips the number from the string given and forces to lowercase. It then use awk to grab the index of the extension from a structured string of valid suffixes. The thing to note is that the string is arranged to multiples of five (so it would need to be widened if more extensions are added), for example k and kb are at indices 2 and 7 respectively.
The index is then reduced by one and modulo'd by five so both k and kb become 1, m and mb become 2 and so-on. That's then used to raised 1024 as a power to get the size in bytes. If the extension was invalid this will resolve to a value of zero, and an extension of b (or nothing) will evaluate to 1.
So long as mod is greater than zero the input string is reduced to only the numeric part and multiplied by the modifier to get the end result.
This is actually how I would probably have solved this originally if I were using a language like PHP, Java etc., it's just a bit of a weird one to put together in a shell script.
I'd still very much appreciate any simplifications though!
Another variation, adding support for decimal values with a simpler T/G/M/K parser for outputs you might find from simpler Unix programs.
to_bytes() {
value=$(echo "$1" | sed -e 's/K//g' | sed -e 's/M//g' | sed -e 's/G//g' | sed -e 's/T//g' )
units=$(echo -n "$1" | grep -o .$ )
case "$units" in
T) value=$(bc <<< "scale=2; ($value * 1024 * 1024 * 1024 * 1024)") ;;
G) value=$(bc <<< "scale=2; ($value * 1024 * 1024 * 1024)") ;;
M) value=$(bc <<< "scale=2; ($value * 1024 * 1024)") ;;
K) value=$(bc <<< "scale=2; ($value * 1024)") ;;
b|'') let 'value += 0' ;;
*)
value=
echo "Unsupported units '$units'" >&2
;;
esac
echo "$value"
}

Convert string into integer in bash script - "Leading Zero" number error

In a text file, test.txt, I have the next information:
sl-gs5 desconnected Wed Oct 10 08:00:01 EDT 2012 1001
I want to extract the hour of the event by the next command line:
hour=$(grep -n sl-gs5 test.txt | tail -1 | cut -d' ' -f6 | awk -F ":" '{print $1}')
and I got "08". When I try to add 1,
14 echo $((hour+1))
I receive the next error message:
./test2.sh: line 14: 08: value too great for base (error token is "08")
If variables in Bash are untyped, why?
See ARITHMETIC EVALUATION in man bash:
Constants with a leading 0 are interpreted as octal numbers.
You can remove the leading zero by parameter expansion:
hour=${hour#0}
or force base-10 interpretation:
$((10#$hour + 1))
what I'd call a hack, but given that you're only processing hour values, you can do
hour=08
echo $(( ${hour#0} +1 ))
9
hour=10
echo $(( ${hour#0} +1))
11
with little risk.
IHTH.
You could also use bc
hour=8
result=$(echo "$hour + 1" | bc)
echo $result
9
Here's an easy way, albeit not the prettiest way to get an int value for a string.
hour=`expr $hour + 0`
Example
bash-3.2$ hour="08"
bash-3.2$ hour=`expr $hour + 0`
bash-3.2$ echo $hour
8
In Short: In order to deal with "Leading Zero" numbers (any 0 digit that comes before the first non-zero) in bash
- Use bc An arbitrary precision calculator language
Example:
a="000001"
b=$(echo $a | bc)
echo $b
Output: 1
From Bash manual:
"bc is a language that supports arbitrary precision numbers with interactive execution
of statements. There are some similarities in the syntax to the C programming lan-
guage. A standard math library is available by command line option. If requested, the
math library is defined before processing any files. bc starts by processing code from
all the files listed on the command line in the order listed. After all files have
been processed, bc reads from the standard input. All code is executed as it is read.
(If a file contains a command to halt the processor, bc will never read from the standard input.)"
Since hours are always positive, and always 2 digits, you can set a 1 in front of it and subtract 100:
echo $((1$hour+1-100))
which is equivalent to
echo $((1$hour-99))
Be sure to comment such gymnastics. :)
The leading 0 is leading to bash trying to interpret your number as an octal number, but octal numbers are 0-7, and 8 is thus an invalid token.
If I were you, I would add some logic to remove a leading 0, add one, and re-add the leading 0 if the result is < 10.
How about sed?
hour=`echo $hour|sed -e "s/^0*//g"`

Shell Script: How do I add the digits of a number?

I am making a shell script that takes a single number (length is unimportant) from the command line and adds the digits of it together. I thought I had it, but it won't work and either displays "0+3+4+5" if the command input is 345 or it displays the variables when I use expr to add them.
#!/bin/bash
sum=0
i="$(expr length $1)"
s=$1
for i in $(seq 0 $((${#s} - 1))); do
value=${s:$i:1}
typeset -i value
sum=$sum+$value
done
echo $sum
Also doesn't work when I replace it with sum='expr $sum + $value'
any ideas?
What you are looking for is sum=$(($sum+$value)).
#!/bin/bash
expr $(echo $1| sed 's/./& + /g;s/..$//')
For example, if the argument is 12345, this translates it to the string 1 + 2 + 3 + 4 + 5 and uses expr to evaluate it.

Resources