comparison of integer and floating point numbers in shell script - linux

In shell script how can we compare (integer and floating point) ,(flaoting point and floating point),(floating point and integer),(integer and integer) with only one if condition.
i have few examples like
set X=3.1
set Y=4.1
if [ $X < $Y ] then
echo "wassup"
endif
But running the above from cron job doesnt seem to work.

The way to carry out floating point operations in bash is to use bc which is available on almost all linux distributions.
# bc will return 0 for false and 1 for true
if [ $(echo "23.3 > 7.3" | bc) -ne 0 ]
then
echo "wassup"
fi
There's a good article available on linux journal about floating point math in bash using bc.

Bah itself only handles integers. Use bc:
echo "$X>$Y" | bc
0
echo "$X<$Y" | bc
1
You don't need to worry about scale. It is just for the preocision of output formats:
X=3.000001
Y=3.0001
echo "$X>$Y" | bc
0
echo "$X<$Y" | bc
1
echo "scale=1;$X<$Y" | bc
1

below example works on bash shell.
X=3.1
Y=4.1
if [ $X -le $Y ]
then
echo "wassup"
fi
you may want to learn shell script here

EDIT. based on the comments to this answer (thanks to user unknown and glenn jackman), it seems that when using bc for a true/false test, the required bash test is simply:
(( $(echo "$X < $Y" |bc) )) ... see the test results and script below
wheras, the comparison to -ne 0 is needed for the old style bash [ ] test.
bash does not natively handle floating point numbers, but you can call a utility such as bc
From man bc - An arbitrary precision calculator language
X=3.1
Y=4.1
# This test has two superfluous components.
# See EDIT (above) and TESTS below
if (($(echo "scale=9; $X < $Y" |bc)!=0)) ;then
echo "wassup"
fi
TEST results:
if [ "1" ] true
[ "1" ] true
if [ "0" ] true
[ "0" ] true
if [ 1 ] true
[ 1 ] true
if [ 0 ] true
[ 0 ] true
if (( "1" )) true
(( "1" )) true
if (( "0" )) false
(( "0" )) false
if (( 1 )) true
(( 1 )) true
if (( 0 )) false
(( 0 )) false
echo "1<1"|bc true
echo "1<0"|bc true
TEST script:
printf 'if [ "1" ] '; if [ "1" ]; then echo true; else echo false; fi
printf ' [ "1" ] '; [ "1" ] && echo true || echo false
printf 'if [ "0" ] '; if [ "0" ]; then echo true; else echo false; fi
printf ' [ "0" ] '; [ "0" ] && echo true || echo false
echo
printf 'if [ 1 ] '; if [ 1 ]; then echo true; else echo false; fi
printf ' [ 1 ] '; [ 1 ] && echo true || echo false
printf 'if [ 0 ] '; if [ 0 ]; then echo true; else echo false; fi
printf ' [ 0 ] '; [ 0 ] && echo true || echo false
echo
printf 'if (( "1" )) '; if (("1")); then echo true; else echo false; fi
printf ' (( "1" )) '; (("1")) && echo true || echo false
printf 'if (( "0" )) '; if (("0")); then echo true; else echo false; fi
printf ' (( "0" )) '; (("0")) && echo true || echo false
echo
printf 'if (( 1 )) '; if (( 1 )); then echo true; else echo false; fi
printf ' (( 1 )) '; (( 1 )) && echo true || echo false
printf 'if (( 0 )) '; if (( 0 )); then echo true; else echo false; fi
printf ' (( 0 )) '; (( 0 )) && echo true || echo false
echo
printf 'echo "1<1"|bc '; echo "1<1"|bc >/dev/null && echo true || echo false
printf 'echo "1<0"|bc '; echo "1<0"|bc >/dev/null && echo true || echo false

Related

can someone help me make this code smaller?

I need to upload this code, but the code is too large and confusing, can someone possibly help me to summarize this code or make it smaller? Its only the input part which's kinda confusing.
input(){
while read line;
do
for name in $line;
do
if [ "$name" == "`cat /etc/passwd | cut -f1 -d: | grep "^$name"`" ]
then
#echo $name----------
if [[ $s -eq 0 && $l -eq 0 && $i -eq 0 && $I -eq 0 && $u -eq 0 ]]; then
standard
fi
if [ $s -eq 1 ]
then
keepSudoUser
fi
if [ $l -eq 1 ]
then
interactiveShell
fi
if [ $i -eq 1 ]
then
userUnder1000
fi
if [ $I -eq 1 ]
then
userOver1000
fi
if [ $u -eq 1 ]
then
excludeUser
fi
fi
NO=0
done
done
}
input
This is more compact:
input() {
while read -ra names; do
for name in "${names[#]}"; do
if grep -q "^${name}:" /etc/passwd; then
[[ "$s$l$i$I$u" == "00000" ]] && standard
(( s == 1 )) && keepSudoUser
(( l == 1 )) && interactiveShell
(( i == 1 )) && userUnder1000
(( I == 1 )) && userOver1000
(( u == 1 )) && excludeUser
fi
NO=0
done
done
}
input
A more strict translation would be
((s == 0 && l == 0 && i == 0 && I == 0 && u == 0)) && standard
# or
(( !(s || l || i || I || u) )) && standard

Multiple conditions in case statement, bash

read X;
read Y;
read Z;
if [[ $X,$Y,$Z -ne "0" ]]; then
if [[ $X,$Y,$Z -ge 1 && $X,$Y,$Z -le 1000 ]] && [[ $((X+Y)) -gt $Z || $((Y+Z)) -gt $X || $((X+Z)) -gt $Y ]]; then
case $X,$Y,$Z in
$X -eq $Y && $Y -eq $Z && $X -eq $Z ) echo "EQUILATERAL";;
esac
else
echo "bye";
fi
fi
*./bashtesting.sh: line 7: syntax error near unexpected token `-eq
./bashtesting.sh: line 7: $X -eq $Y && $Y -eq $Z && $X -eq $Z ) echo "EQUILATERAL";;*
how to equate the three variables at a time?
This is how I would do it, with a loop and associative array.
#!/usr/bin/env bash
for h in x y z; do
read -rp 'Enter input: ' input
if [[ -z $input ]]; then
printf >&2 '%s is empty, please try again!\n' "${input:-input}"
exit 1
elif [[ $input != *[0-9]* ]]; then
printf '%s is not an int, please try again!\n' "$input"
exit 1
else
declare -A num[$h]=$input
fi
done
for i in "${num[#]}"; do
if ! (( i == 0 )); then
for j in "${num[#]}"; do
if (( j > 1 && j < 100 )); then
if (( (num[x] + num[y]) > num[z] || (num[y] + num[z]) > num[x] || (num[x] + num[z]) > num[y] )); then
if (( num[x] == num[y] && num[y] == num[x] && num[x] == num[z] )); then
echo equilateral
break 2
fi
fi
fi
done
fi
done
It it not pretty, It looks like an Anti Pattern, and there might be a better way to do it but this will get you there I suppose.

How to improve generation speed of PS1 in this code?

PROMPT_COMAND runs slow. Code optimization needed.
I use bash builtin PROMPT_COMMAND to customize PS1. Too much echo | grep commands were executed to get colorful git branch indication.The prompt runs much slower. Any ideas about optimazation?
function git_branch {
branch="`git branch 2>/dev/null | grep "^\*" | sed -e "s/^\*\ //"`"
if [ "${branch}" != "" ];then
if [ "${branch}" = "(no branch)" ];then
branch="(`git rev-parse --short HEAD`...)"
fi
echo "$branch"
fi
}
# display last two dentry
function get_curdir {
dir=$(pwd);
last_dirent=${dir##*/}
dir=${dir%/*}
lastbutone_dirent=${dir##*/}
echo -n "${lastbutone_dirent}/${last_dirent}"
}
# colours
RED="\[\033[1;31m\]"
GREEN="\[\033[1;32m\]"
BROWN="\[\033[0;33m\]"
BLUE="\[\033[1;34m\]"
PURPLE="\[\033[1;35m\]"
CYAN="\[\033[1;36m\]"
GRAY="\[\033[0;37m\]"
LIGHT_RED="\[\033[0;31m\]"
LIGHT_GREEN="\[\033[1;32m\]"
LIGHT_BLUE="\[\033[0;34m\]"
YELLOW="\[\033[1;33m\]"
LIGHT_PURPLE="\[\033[1;35m\]"
LIGHT_CYAN="\[\033[1;36m\]"
LIGHT_GRAY="\[\033[0;37m\]"
WHITE="\[\033[1;37m\]"
RESTORE="\[\033[0m\]" #0m restores to the terminal's default colour
function prompt_command {
RET=$?
PS1="${CYAN}[${RESTORE}${GREEN}\u${RESTORE} "
PS1+="${BLUE}$(get_curdir)${RESTORE}${CYAN}]${RESTORE}"
BRANCH=$(git_branch)
if [ "${BRANCH}" != "" ]; then
STATUS="$(git status -s 2>/dev/null)"
if echo "${STATUS}" | grep -e "??"; then
PARENTHESES_COLOR=${RED}
else
PARENTHESES_COLOR=${GREEN}
fi >/dev/null
if [ "${STATUS}" = "" ]; then
BRANCH_COLOR=${GREEN}
else
if echo "${STATUS}" | grep -e "^[ M]M"; then
if echo "${STATUS}" | grep -e "^M[ M]"; then
BRANCH_COLOR=${PURPLE}
else
BRANCH_COLOR=${RED}
fi
elif echo "${STATUS}" | grep -e "^M "; then
BRANCH_COLOR=${YELLOW}
fi
fi >/dev/null
PS1+="${PARENTHESES_COLOR}(${RESTORE}${BRANCH_COLOR}$(git_branch)${RESTORE}${PARENTHESES_COLOR})${RESTORE}"
fi
if [ "$RET" = "0" ]; then
PS1+=" ${CYAN}->${RESTORE} "
else
PS1+=" ${RED}->${RESTORE} "
fi
}
export PROMPT_COMMAND=prompt_command
update
I run a simple test in some dir where a git repo resides and use time to get time cost.
for ((i=0;i<10000;i++));do
prompt_command;
done
Performance of the original version above is:
real 3m4.567s
user 1m32.698s
sys 3m2.495s
Then I change [] to [[ ]], diff like this:
17,18c17,18
< if [ "${branch}" != "" ];then
< if [ "${branch}" = "(no branch)" ];then
---
> if [[ "${branch}" != "" ]];then
> if [[ "${branch}" == "(no branch)" ]];then
58c58
< if [ "${BRANCH}" != "" ]; then
---
> if [[ "${BRANCH}" != "" ]]; then
65c65
< if [ "${STATUS}" = "" ]; then
---
> if [[ "${STATUS}" == "" ]]; then
81c81
< if [ "$RET" = "0" ]; then
---
> if [[ "$RET" == "0" ]]; then
Performance get a little worse
real 3m7.690s
user 1m30.717s
sys 3m6.676s
So, [] doesn't matter.
But builtin regex helps a lot.
When I changes to the following and replace $(pwd) with $PWD
if [ "${BRANCH}" != "" ]; then
regex_untracked=".*^\?\?.*"
regex_staged=".*^M[ M].*"
regex_modified=".*^[ M]M.*"
STATUS="$(git status -s 2>/dev/null)"
if [[ ${STATUS} =~ $regex_untracked ]]; then
PARENTHESES_COLOR=${RED}
else
PARENTHESES_COLOR=${GREEN}
fi >/dev/null
if [[ ${STATUS} =~ $regex_modified ]]; then
if [[ ${STATUS} =~ $regex_staged ]]; then
BRANCH_COLOR=${PURPLE}
else
BRANCH_COLOR=${RED}
fi
elif [[ ${STATUS} =~ $regex_staged ]]; then
BRANCH_COLOR=${YELLOW}
else
BRANCH_COLOR=${GREEN}
fi >/dev/null
PS1+="${PARENTHESES_COLOR}(${RESTORE}${BRANCH_COLOR}$(git_branch)${RESTORE}${PARENTHESES_COLOR})${RESTORE}"
fi
Time consumed decreases:
real 2m15.534s
user 1m1.036s
sys 2m15.043s
By the way, without this colorful branch feature, performance is
real 1m0.478s
user 0m29.499s
sys 1m1.411s
bash has its own built-in regular expression matching. For example, replace
echo "${STATUS}" | grep -e "^[ M]M"
with
regex="^[ M]M"
[[ $STATUS =~ $regex ]]

IF statement multiple and or conditions

I am using if statement with multiple condition in bash.
How can I reduce the following line syntax. So that it looks good from design point of you.
if [ "$1" != "-l" ] && [ "$1" != "-a" ] && [ "$1" != "-h" ] && [ "$1" != "" ] && [ "$1" = "-d" ] || [ "$1" = "-mv" ] || [ "$1" = "-dv" ] || [ "$1" = "-mr" ] || [ "$1" = "-dr" ];
Thanks
Use pattern matching.
if [[ $1 && $1 != -[lah] && $1 == -#(d|mv|dv|mr|dr) ]]; then
#(...) is an example of an extended pattern, which should be recognized by default inside [[ ... ]] in recent versions of bash. If you version is not so recent, add shopt -s extglob to the beginning of your script.
In fact, you can drop the $1 && $1 != -[lah] because its truth would be implied by the truth of $1 == -#(...).
if [[ $1 == -#(d|mv|dv|mr|dr) ]]; then
You could also just use a POSIX-compliant case statement:
case $1 of
-d|-mv|-dv|-mr|-dr) echo good option ;;
*) echo bad option ;;
esac
You can create 2 arrays for matching and non matching values and check if element $1 matches any element in the array or not like below.
nonmatch_array=( "-l" "-a" "-h" "" )
match_array=( "-d" "-mv" "-dv" "-mr" "-dr" )
if [ `echo ${match_array[#]} | grep "$1"` ] || ! [ `echo ${nonmatch_array[#]} | grep "$1"` ] ; then
echo "is in array"
else
echo "is not in array"
fi
Hope it should work for you.
First try to limit the length of the code on 1 line.
if [ [ "$1" != "-l" ]
&& [ "$1" != "-a" ]
&& [ "$1" != "-h" ]
&& [ -n "$1" ]
&& ( [ "$1" = "-d" ]
|| [ "$1" = "-mv" ]
|| [ "$1" = "-dv" ]
|| [ "$1" = "-mr" ]
|| [ "$1" = "-dr" ] ) ];
I added braces, to make clear what you mean with the or's.
Now you can combine all matches with a regular expression:
if [[ ! ("$a" =~ ^-(l|a|h|d|)$)
&& "$a" =~ ^-(mv|dv|mr|dr)$ ]]; then
echo "Yes $a matches"
fi
but reconsider what you are testing. The test will only be true when it matches -mv/-dv/-mr/-dr, so you do not need to test for the options lah.
if [[ "$a" =~ ^-(d|mv|dv|mr|dr)$ ]]; then
echo "Yes $a matches"
fi
You can use a variable for extracting the options:
options="d|mv|dv|mr|dr"
if [[ "$a" =~ ^-(${options})$ ]]; then
echo "Yes $a matches"
fi
Everytime the code is becoming hard to read (and also for long code or repeating statements), you should consider using a function.
The next function is short, but hard to read:
options="d|mv|dv|mr|dr"
function checkoption1 {
[[ "$a" =~ ^-(${options})$ ]]
}
checkoption1 "$a" &&
echo "Yes $a matches"
I would choose for a slightly more verbose function. I will include your original tests for lah for showing the possibilities.
# checkoption return 0 for match,
# returns 1 for forbidden option
# returns 2 for undefined option
function checkoption2 {
case "$1" in
-d|-mv|-dv|-mr|-dr) return 0 ;;
-l|-a|-h|"") return 1;;
*) return 2;;
esac
}
checkoption2 "$a" &&
echo "Yes $a matches"
You should make some testruns before accepting your code.
I have made some tests with a small loop (now all answers together)
function checkoption1 {
[[ "$a" =~ ^-(${options})$ ]]
}
# checkoption return 0 for match,
# returns 1 for forbidden option
# returns 2 for undefined option
function checkoption2 {
case "$1" in
-d|-mv|-dv|-mr|-dr) return 0 ;;
-l|-a|-h|"") return 1;;
*) return 2;;
esac
}
for a in -mv mv -mvx -ms -mr -dr; do
if [[ ! ("$a" =~ ^-(l|a|h|)$)
&& "$a" =~ ^-(d|mv|dv|mr|dr)$ ]]; then
echo "Yes $a matches"
fi
if [[ "$a" =~ ^-(d|mv|dv|mr|dr)$ ]]; then
echo "Yes $a matches"
fi
options="d|mv|dv|mr|dr"
if [[ "$a" =~ ^-(${options})$ ]]; then
echo "Yes $a matches"
fi
checkoption1 "$a" &&
echo "Yes $a matches"
checkoption2 "$a" &&
echo "Yes $a matches 2"
done

checking if a string is a palindrome

I am trying to check if a string is a palindrome in bash. Here is what I came up with:
#!/bin/bash
read -p "Enter a string: " string
if [[ $string|rev == $string ]]; then
echo "Palindrome"
fi
Now, echo $string|rev gives reversed string. My logic was to use it in the condition for if. That did not work out so well.
So, how can I store the "returned value" from rev into a variable? or use it directly in a condition?
Another variation without echo and unnecessary quoting within [[ ... ]]:
#!/bin/bash
read -p "Enter a string: " string
if [[ $(rev <<< "$string") == "$string" ]]; then
echo Palindrome
fi
A bash-only implementation:
is_palindrome () {
local word=$1
local len=$((${#word} - 1))
local i
for ((i=0; i <= (len/2); i++)); do
[[ ${word:i:1} == ${word:len-i:1} ]] || return 1
done
return 0
}
for word in hello kayak; do
if is_palindrome $word; then
echo $word is a palindrome
else
echo $word is NOT a palindrome
fi
done
Inspired by gniourf_gniourf:
is_palindrome() {
(( ${#1} <= 1 )) && return 0
[[ ${1:0:1} != ${1: -1} ]] && return 1
is_palindrome ${1:1: 1}
}
I bet the performance of this truly recursive call really sucks.
Use $(command substitution):
#!/bin/bash
read -p "Enter a string: " string
if [[ "$(echo "$string" | rev)" == "$string" ]]; then
echo "Palindrome"
fi
Maybe it is not the best implementation, but if you need something with pure sh
#!/bin/sh
#get character <str> <num_of_char>. Please, remember that indexing is from 1
get_character() {
echo "$1" | cut -c "$2"
}
for i in $(seq $((${#1} / 2))); do
if [ "$(get_character "$1" "$i")" != "$(get_character "$1" $((${#1} - i + 1)))" ]; then
echo "NO"
exit 0
fi
done
echo "YES"
and canonical way with bash as well
for i in $(seq 0 $((${#1} / 2 - 1))); do
if [ "${1:$i:1}" != "${1:$((${#1} - i - 1)):1}" ]; then
echo "NO"
exit 0
fi
done
echo "YES"
Skipping all punctuation marks and letter case.
input:He lived as a devil, eh?
output:Palindrome
input:Madam, I am Adam.
output:Not Palindrome
#!/bin/bash
#set -x
read -p "Enter a sentence" message
message=$(echo "$message" | \
sed -e '
s/[[:space:]]//g
s/[[:punct:]]//g
s/\!//g
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
' )
i=0
while read -n 1 letter
do
tempArray[i]="$letter"
((i++))
done < <(echo "$message")
i=0
counter=$((${#message}-1))
while [ "$i" -ne $((${#message}/2)) ]
do
if [ "${tempArray[$i]}" = "${tempArray[$counter]}" ]
then
((i++))
((counter--))
else echo -n "Not ";break
fi
done
echo "Palindrome"
exit

Resources