How to use mod operator in bash? - bash

I'm trying a line like this:
for i in {1..600}; do wget http://example.com/search/link $i % 5; done;
What I'm trying to get as output is:
wget http://example.com/search/link0
wget http://example.com/search/link1
wget http://example.com/search/link2
wget http://example.com/search/link3
wget http://example.com/search/link4
wget http://example.com/search/link0
But what I'm actually getting is just:
wget http://example.com/search/link

Try the following:
for i in {1..600}; do echo wget http://example.com/search/link$(($i % 5)); done
The $(( )) syntax does an arithmetic evaluation of the contents.

for i in {1..600}
do
n=$(($i%5))
wget http://example.com/search/link$n
done

You must put your mathematical expressions inside $(( )).
One-liner:
for i in {1..600}; do wget http://example.com/search/link$(($i % 5)); done;
Multiple lines:
for i in {1..600}; do
wget http://example.com/search/link$(($i % 5))
done

This might be off-topic. But for the wget in for loop, you can certainly do
curl -O http://example.com/search/link[1-600]

Math in bash: how to use all bash operators, and arithmetic expansion, in bash
Of the 346k visitors to this question thus far, I'd be willing to bet 344.9k of them just want the title of this question answered 😃:
How to use mod operator in bash?
Even I googled "bash modulus" looking for that answer, and landed here. So, now that I've figured it out, let's just jump straight to it:
How to use the modulus (%) operator in bash
Just do this, for instance:
# 7 mod 4 (answer is 3, but to print the output you must use one of the cmds
# below)
$((7 % 4))
# [PREFERRED: no quotes]
# print the result (double quotes are not required)
echo $((7 % 4))
# print the result (with double quotes if you like)
echo "$((7 % 4))"
Example with variables:
num1="7"
num2="4"
# [PREFERRED: no $ signs nor extraneous quotes] result is 3
echo $((num1 % num2))
# Also ok: with $ signs
echo $(($num1 % $num2))
# Also ok: with $ signs and extra quotes
echo "$(("$num1" % "$num2"))"
Store the result into a variable:
mod=$((num1 % num2))
echo "$mod" # result is 3
The main links to study for these concepts are these, from the official GNU bash user manual:
Bash Arithmetic Expansion
Bash Shell Arithmetic
More on bash "arithmetic expansion"
I learned the above from #Mark Longair's answer (although it took me some effort to comprehend it all), and that's where I got the link just below. I then did more research.
The $(( )) part is called "Arithmetic Expansion", and is described in the official GNU bash user manual here: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arithmetic-Expansion.
Basic examples (place echo in front of each one to see the result print to the screen):
# general form
$((mathematical_expression))
# addition
$((7 + 4)) # 11
# subtraction
$((7 - 4)) # 3
# modulus (remainder)
$((7 % 4)) # 3
# logical AND
$((7 && 4)) # 1
# bitwise AND
$((7 & 4)) # 4
# etc.
# See the full operator list below for more
Double quotes around the arithmetic expansion are not needed. From the manual above (emphasis added):
The expression is treated as if it were within double quotes, but a double quote inside the parentheses is not treated specially. All tokens in the expression undergo parameter and variable expansion, command substitution, and quote removal. The result is treated as the arithmetic expression to be evaluated. Arithmetic expansions may be nested.
For all shell arithmetic operators, see the "Shell Arithmetic" section of the GNU bash manual here: https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Arithmetic
You essentially have all of the C language mathematical operators at your disposal. The arithmetic is done "in fixed-width integers with no check for overflow", so if you do echo $((11/10)) or echo $((19/10)) you'll get 1 in both cases since the fractional part is truncated for integers.
From the manual link just above (emphasis added):
Evaluation is done in fixed-width integers with no check for overflow, though division by 0 is trapped and flagged as an error. The operators and their precedence, associativity, and values are the same as in the C language.
Since the arithmetic operators in bash have the same precedence as in C, as it states above, you can also reference the C Operator Precedence from the cppreference community wiki here: https://en.cppreference.com/w/c/language/operator_precedence <-- put that in your toolbag.
Shell Arithmetic: here are all of the supported operators from the GNU Bash manual
They are listed in order of highest to lowest precedence:
id++ id--
variable post-increment and post-decrement
++id --id
variable pre-increment and pre-decrement
- +
unary minus and plus
! ~
logical and bitwise negation
**
exponentiation
* / %
multiplication, division, remainder
+ -
addition, subtraction
<< >>
left and right bitwise shifts
<= >= < >
comparison
== !=
equality and inequality
&
bitwise AND
^
bitwise exclusive OR
|
bitwise OR
&&
logical AND
||
logical OR
expr ? expr : expr
conditional operator
= *= /= %= += -= <<= >>= &= ^= |=
assignment
expr1 , expr2
comma
Using alternate bases in your arithmetic, such as binary (base-2), octal (base-8), and hex (base-16)
To learn about using different bases, such as base-2 (binary), base-8 (octal) or base-16 (hex) with the bash arithmetic operators, read the next couple paragraphs below the "Shell Arithmetic" list above in the manual.
Here are a few quick examples with input numbers which are decimal (base-10), octal (base-8), hex (base-16), and binary (base-2), used in the math:
# hex 0xa (decimal 10) + decimal 5 = decimal 15
echo $((0xa + 5)) # prints `15` (decimal 15)
# OR (same thing)
echo $((16#a + 5)) # prints `15` (decimal 15)
# octal 071 (decimal 57) + hex 0xaa (decimal 170) = decimal 227
echo $((071 + 0xaa)) # prints `227` (decimal 227)
# OR (same thing)
echo $((8#71 + 16#aa)) # prints `227` (decimal 227)
# binary 1011 (decimal 11) + decimal 2 = decimal 13
echo $((2#1011 + 2)) # prints `13` (decimal 13)
# binary 1111 (decimal 15) + binary 11111 (decimal 31) = decimal 46
echo $((2#1111 + 2#11111)) # prints `46` (decimal 46)
To print as hex, use printf "0x%X\n" number:
# prints `0x2E` (hex 2E, or decimal 46)
printf "0x%X\n" $((2#1111 + 2#11111))
To print as binary, use bc (see my answer here):
# prints `0b101110` (decimal 46)
printf "0b%s\n" "$(echo "obase=2; $((2#1111 + 2#11111))" | bc)"

This post is rather old but I thought I would contribute since I stumbled upon it while trying to research the same issue of setting keyboard color through automation.
I created a simple BASH script that I call from my ROOT chrontab every minute to set the keyboard color as the day progresses. You can tweak the color patterns and the modulo to match your needs. This is just a good starting point.
#!/bin/bash
# must run as ROOT to work
# put in your root crontab to change the color at set times
sec=$(date +%s)
min=$(( $sec / 60 ))
col=$(( $min % 7 ))
colors=('0000FF' '00FF00' '00FFFF' 'FF0000' 'FF00FF' 'FFFF00' 'FFFFFF')
colorFile="/sys/class/leds/system76_acpi::kbd_backlight/color"
if [ -f "$colorFile" ]; then
echo "Set keyboard to color $col ~ ${colors[$col]}"
echo "${colors[$col]}" > "$colorFile"
fi
Hope you like it.

Related

floating point math in bash with variables [duplicate]

This question already has answers here:
How do I use floating-point arithmetic in bash?
(23 answers)
Bash - Calculate the Average of Numbers Inputted
(4 answers)
Closed 1 year ago.
How do you perform floating-point math over variables in bash
the output I get is an integer like number
#! /bin/bash
# finding the average of n numbers
avg=0
read -p "" n
for (( i = 0; i < $n; i++ ))
do
read x
((avg = $avg + $x ))
done
#printf %.3f "$(( avg / n )) "
the goal is to show up to 3 decimal places
3
4
5
6
./avg.sh: line 22: printf: 5 : invalid number
5,000
I tried using | bc but I am missing sth
You can replace the entire loop with awk, which does floating-point math natively:
read -p "" n
awk -v n="$n" '{sum+=$1}; NR==n {printf "%.3f\n", sum/n; exit}'
Explanation: -v n="$n" copies the shell variable n into an awk variable with the same name. {avg+=$1} adds each line of input (well, the first "field" of each line) to a running sum. NR==n {printf "%.3f\n", sum/n; exit} means when it hits the nth line, it prints sum/n to three decimal places and exits.
Found this useful question
Added this section after the for loop
var=$(echo "scale=3; $avg / $n" | bc -l)
echo $var
Here is how you can compute the average of n numbers with POSIX shell arithmetic and have 3 or more decimals:
#!/usr/bin/env sh
decimals=3
# The decimal precision is one digit more than the display decimals,
# so it can have an accurate rounding when formatting.
# The precision factor is 10^(decimals + 1).
precision_factor=$(printf '1%0*d' $((decimals + 1)) 0)
average () {
# Multiply the sum of the arguments by the precision factor,
# and divide by the number of arguments.
# With IFS=+ $* expands to the sum of the arguments $1+$2+$3...
# Example, arguments 5 8 1 7 will expand to 5+8+1+7 because IFS is +
IFS=+ average=$(((($*)*precision_factor)/$#))
# The integer part is the average divided by the precision factor.
integer_part=$((average/precision_factor))
# The decimal part is the average head-stripped from the integer part.
decimal_part=${average#$integer_part}
# Assemble a C-locale (decimal point) string for floating-point average.
float_average="$integer_part.$decimal_part"
# Bash consider floating point arguments to printf must be formatted
# to the current locale. So specify we use the C-locale.
LC_NUMERIC=C printf 'Average: %.*f\n' "$decimals" "$float_average"
}
if [ $# -eq 0 ]; then
printf 'Enter a set of numbers: '
read -r input
average $input
else
average "$#"
fi

How to make single-digit cents have a leading zero while doing modulus?

RAW_AMT=000078753603
I need amt = 787536.03
This is the code:
AMT=$(${EXPR} ${RAW_AMT} / 100).$(${EXPR} ${RAW_AMT} % 100)
but it displays as AMT = 787536.3
printf can be used to format numbers in any way you choose. In particular, printf '%02d' prints a value with two digits, padding with a zero on the left.
Writing this for bash in a robust and efficient manner (no forks, no execs, no implicit temporary files) may look like:
#!/usr/bin/env bash
shopt -s extglob # enable extglob syntax
raw_amt=000078753603 # original input value
unpadded_amt=${raw_amt##+(0)} # trim leading 0's -- otherwise a value that starts with
# 0s can be treated as octal rather than decimal.
# use a format string to control formatting of our value
printf -v amt '%d.%02d' "$(( unpadded_amt / 100 ))" "$(( unpadded_amt % 100 ))"
echo "$amt"
...or, a less efficient implementation compatible with POSIX sh:
#!/bin/sh
raw_amt=000078753603 # original input value
# remove trailing zeros. This requires a fork, but not an exec on any shell
# where expr is builtin
unpadded_amt=$(expr "$raw_amt" : '0*\([^0].*\)$')
# use a format string to control formatting of our value
amt=$(printf '%d.%02d' "$(( unpadded_amt / 100 ))" "$(( unpadded_amt % 100 ))")
echo "$amt"
using awk is a bit easier:
kent$ raw=000078753603
kent$ awk '{$0*=1;sub(/..$/,".&")}7' <<<$raw
787536.03
$0*=1 will remove the leading zeros
sub(...) will add a point before ..$
7 a non-zero number, will execute awk's default action, print the result out.

How to round a large number in Shell command?

In Mac terminal, I would like to round a large number.
For example,
At 10^13th place:
1234567812345678 --> 1230000000000000
Or at 10^12th place:
1234567812345678 --> 1235000000000000
So I would like to specify the place, and then get the rounded number.
How do I do this?
You can use arithmetic expansion:
$ val=1234567812345678
$ echo $(( ${val: -13:1} < 5 ? val - val % 10**13 : val - val % 10**13 + 10**13 ))
1230000000000000
$ echo $(( ${val: -12:1} < 5 ? val - val % 10**12 : val - val % 10**12 + 10**12 ))
1235000000000000
This checks if the most significant removed digit is 5 or greater, and if it is, the last significant unremoved digit is increased by one; then we subtract the division remainder from the (potentially modified) initial value.
If you don't want to have to write it this way, you can wrap it in a little function:
round () {
echo $(( ${1: -$2:1} < 5 ? $1 - $1 % 10**$2 : $1 - $1 % 10**$2 + 10**$2 ))
}
which can then be used like this:
$ round "$val" 13
1230000000000000
$ round "$val" 12
1235000000000000
Notice that quoting $val isn't strictly necessary here, it's just a good habit.
If the one-liner is too cryptic, this is a more readable version of the same:
round () {
local rounded=$(( $1 - $1 % 10**$2 )) # Truncate
# Check if most significant removed digit is >= 5
if (( ${1: -$2:1} >= 5 )); then
(( rounded += 10**$2 ))
fi
echo $rounded
}
Apart from arithmetic expansion, this also uses parameter expansion to get a substring: ${1: -$2:1} stands for "take $1, count $2 from the back, take one character". There has to be a space before -$2 (or is has to be in parentheses) because otherwise it would be interpreted as a different expansion, checking if $1 is unset or null, which we don't want.
awk's [s]printf function can do rounding for you, within the limits of double-precision floating-point arithmetic:
$ for p in 13 12; do
awk -v p="$p" '{ n = sprintf("%.0f", $0 / 10^p); print n * 10^p }' <<<1234567812345678
done
1230000000000000
1235000000000000
For a pure bash implementation, see Benjamin W.'s helpful answer.
Actually, if you want to round to n significant digits you might be best served by mixing up traditional math and strings.
Serious debugging is left to the student, but this is what I quickly came up with for bash shell and hope MAC is close enough:
function rounder
{
local value=$1;
local digits=${2:-3};
local zeros="$( eval "printf '0%.0s' {1..$digits}" )"; #proper zeros
# a bit of shell magic that repats the '0' $digits times.
if (( value > 1$zeros )); then
# large enough to require rounding
local length=${#value};
local digits_1=$(( $digits + 1 )); #digits + 1
local tval="${value:0:$digits_1}"; #leading digits, plus one
tval=$(( $tval + 5 )); #half-add
local tlength=${#tval}; #check if carried a digit
local zerox="";
if (( tlength > length )); then
zerox="0";
fi
value="${tval:0:$digits}${zeros:0:$((length-$digits))}$zerox";
fi
echo "$value";
}
See how this can be done much shorter, but that's another exercise for the student.
Avoiding floating point math due to the inherit problems within.
All sorts of special cases, like negative numbers, are not covered.

expr displaying the expression rather than solving it

Here is my shell script,
#! /bin/sh
# basic calculator
echo "Please input your choice"
printf " 1.Addition \n 2.SUbstraction \n 3.Multiplication \n 4.Division\n"
read choice
case "$choice" in
1) echo "Enter number 1:";read n1;echo "Enter number 2:";read n2;t=$(expr "$n1"+"$n2");echo "$n1+$n2=$t";;
2) echo "Enter number 1:";read n1;echo "Enter number 2:";read n2;t='expr $n1-$n2';echo "$n1-$n2=$t";;
3) echo "Enter number 1:";read n1;echo "Enter number 2:";read n2;t='expr $n1\*$n2';echo "$n1*$n2=$t";;
4) echo "Enter number 1:";read n1;echo "Enter number 2:";read n2;t='expr $n1/$n2';echo "$n1/$n2=$t";;
esac
Here is my output,
Script started on Sunday 08 November 2015 12:05:21 PM IST
Please input your choice
1.Addition
2.SUbstraction
3.Multiplication
4.Division
1
Enter number 1:
5
Enter number 2:
6
5+6=5+6
The problem is that my expr isnt actually solving the expressions
Whitespace matters:
$ expr 5+6
5+6
$ expr 5 + 6
11
To do arithmetic, you need to give expr 3 distinct arguments.
In some versions of expr it's recommended to use shell arithmetic instead:
$ echo $((5+6))
11
$ echo $((5>=6))
0
Using shell arithmetic using spaces to separate ints is not necessary if desired.
The expr utility makes no lexical distinction between arguments which
may
be operators and arguments which may be operands. An operand which is
lexically identical to an operator will be considered a syntax error.
The syntax of the expr command in general is historic and inconvenient.
New applications are advised to use shell arithmetic rather than expr
You are using single quotes (') instead of backticks ( ` ) for the other calls to expr. But as I'L'l points out, there's almost never any reason to use expr to perform arithmetic.

shell script + numbers sum

what the best simple elegant way to sum number in ksh or bash
my example is about let command , but I want to find better way to summary all numbers
for example
num1=1232
num2=24
num3=444
.
.
.
let SUM=$num1+num2+num3.........
How about:
num1=1232
num2=24
num3=444
sum=$((num1+num2+num3))
echo $sum # prints 1700
Agree with ghostdog74. I once used $(( )) built-in function, but I changed to bc because the format the we receive data is not very "number-formated". Check below:
jyzuz#dev:/tmp> echo $(( 017 + 2 ))
17
jyzuz#dev:/tmp> echo $(( 17 + 2 ))
19
jyzuz#dev:/tmp>
Seems that in the 1st case it understands as binary or hex numbers.. not very sure.
So I changed to bc. You can choose wich way you prefer:
bc << EOF
$num1 + $num2 + $num3
EOF
or
bc <<< "$num1 + $num2 + $num3"
There are other cools ways to do this...but it would be good if you send more details, like if you're performing division also, you'll need to add bc -l argument, to load math lib.
You can eliminate the last dollar sign and freely space the operands and operators (including the variable and the assignment operator) for readability if you move the double parentheses all the way to the outside.
num1=1232
num2=24
num3=444
(( sum = num1 + num2 + num3 ))
(( count++ ))
(( sum += quantity ))
You can't use the increment style operators (*= /= %= += -= <<= >>= &= ^= |= ++ --) unless you use let or the outside (()) form (or you're incrementing variables or making assignments on the right hand side).
you can use $(()) syntax, but if you have decimal numbers, use awk, or bc/dc to do your maths, "portably".

Resources