As an exercise, I'm trying to re-write this code without the compound square brackets, using grep and test. Need to know what to do about the "equal-tilde".
#!/bin/bash
# test-integer2: evaluate the value of an integer.
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
if [ "$INT" -eq 0 ]; then
echo "INT is zero."
else
if [ "$INT" -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
The "equal-tilde" is explained in man bash 's "Compound Commands" paragraph:
You can use your regex (almost) as given in a grep command:
grep "^[-0-9]\+$" <<< "$INT"
The + needs to be escaped as grep uses basic regexes by default. For the rest of your request, just replace the [ with test and drop the ] . Please be aware that your script is missing two fi statements. And, reasonable indentation improves readability and understandability of your code.
if grep "^[-0-9]\+$" <<< "$INT"
then if test "$INT" -eq 0
then echo "INT is zero."
elif test "$INT" -lt 0
then echo "INT is negative."
else echo "INT is positive."
fi
else echo "No int"
fi
Instead of converting the ERE to a BRE (that allows some unintended strings like -5-2 , --- , and even just - to be considered to be a valid number), we could just tell grep to process its input treating the regex as an ERE and to exit with a zero exit code if and only if a matching line was found:
if grep -Eq '^-?[0-9]+$' <<< "$INT"; then
P.S. But, of course, Xubuntu56 will also have to fix his/her code to have an fi to match each if .
grep is an external command, that's a (small) overhead.
To only use shell builtins, you can try to convert the RE to a glob (or multiple globs), and use it in a case-esac.
The glob match is less powerful, and often will look ugly - a reason to hide it in a function.
is_int(){
# error if $1
# contains a character that is not a dash or digit
# is empty
# is dash only
# has a dash not at the first position
case $1 in
*[!-0-9]*|""|-|*?-*) return 1;;
esac
return 0
}
INT=-5
if is_int "$INT"; then
if [ "$INT" -eq 0 ]; then
echo "INT is zero."
elif [ "$INT" -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
fi
This suggestion relies on bash parameter expansion and test, and gets rid of the need for grep altogether. On the down side you need an intermediary variable:
zzz=${INT#-} # removes the negative sign, if it exists
if test -z "${zzz//[0-9]/}" # removes all digits; resulting string should be zero length
then
test ${INT} -lt 0 && printf "%s is negative\n" ${INT}
test ${INT} -eq 0 && printf "%s is zero\n" ${INT}
test ${INT} -gt 0 && printf "%s is positive\n" ${INT}
fi
Naturally the ${param//string/replacement} construct isn't POSIX-compliant.
Another alternative to using grep is expr:
expr "$INT" : '-\?[0-9][0-9]*$'
but while that works with GNU expr, the Solaris versions (there are FOUR versions on my Sol 10 system!) do not accept the \? construct.
Relative to this thread as you have used 'case....esac':
What is the reasoning behind ;; , why 2 semicolons instead of 1, after each command inside a 'case' statement?
I have always wondered as I can't find any mention of it.
A question to help our newbie friend to understand too.
Last login: Wed Jan 23 12:04:15 on ttys000
AMIGA:amiga~> help case
case: case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
Selectively execute COMMANDS based upon WORD matching PATTERN. The
`|' is used to separate multiple patterns.
AMIGA:amiga~> _
And from the manual, (man bash):
case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac
A case command first expands word, and tries to match it against
each pattern in turn, using the same matching rules as for path-
name expansion (see Pathname Expansion below). The word is
expanded using tilde expansion, parameter and variable expan-
sion, arithmetic substitution, command substitution, process
substitution and quote removal. Each pattern examined is
expanded using tilde expansion, parameter and variable expan-
sion, arithmetic substitution, command substitution, and process
substitution. If the shell option nocasematch is enabled, the
match is performed without regard to the case of alphabetic
characters. When a match is found, the corresponding list is
executed. After the first match, no subsequent matches are
attempted. The exit status is zero if no pattern matches. Oth-
erwise, it is the exit status of the last command executed in
list.
please regard ;; as a fix syntax element to separate "cases". You can't use single semicolons as the "list" (in your man bash excerpt) can consist of several commands separated by ; .
@RudiC
The standard shell glob only mentions the ! as a negation of the following character set. The ^ is an extension in bash (and maybe some other shells).
case "$INT" in
*[!0-9-]*|?*-*) echo no int;;
-[0-9]*) echo negative;;
[0-9]*) echo positive;;
*) echo null;;
esac
@apmcd47
expr uses BRE that defines \{m,n\}
The GNU-extensions \? and \+ are generally \{0,1\} and \{1,\}
Unfortunately expr needs a work-around for a leading dash in its first argument
expr X"$INT" : X'-\{0,1\}[0-9]\{1,\}$'
And expr is an external command as well.
@wisecracker
You even posted the man page that mentions the list ;;
A list is a list of commands, separated with newline or ; (as usual).
@MadeInGermany: Thanks for pointing that out. I double checked before posting; man bash
, and I (frequently but not always) test proposals on two systems (Linux and FreeBSD), but I'm afraid doing so on all possible systems / configurations is beyond me.
Thanks as well for extending the logics as I alluded to. Be aware that "-0" is identified as "negative" with your approach..
#!/bin/bash
INT=9
if grep "^[-0-9]\+$" <<< "$INT"; then
if test "$INT" -eq 0; then
echo "INT is zero."
else
if test "$INT" -lt 0; then
echo "INT is negative."
else
echo "INT is positive."
fi
if test $((INT % 2)) -eq 0; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
else
echo "INT is not an integer." >&2
exit 1
fi
I have found now:
[!a-z] works in all Bourne-derived shells, only an interactive zsh does history substitution (and needs [^a-z] or "setopt no_bang_hist").
the [^a-z] works in ksh93 and dash-0.5.8 and zsh in addition to the [!a-z].
A curious example:
dash-0.5.8 has got the ^ in addition, but its man page still says
@Xubuntu56
I do not understand why you want the builtin [[ =~ ]] replaced with grep but introduce the <<< operator that is again specific to bash.
On the only 1's complement machines I've ever worked on (CDC 3600 and CDC 6500) you could have both -0 and +0. And if you used the machine language instructions to compare integer values, they tested equal (48-bit integers on the 3600 and 60-bit integers on the 6500). If you use the machine language instructions to compare their bitwise values, they tested unequal (since the sign bit was different).
I am experimenting with this in the shell ATM and does indeed have -0 : Minifloat - Wikipedia
Similarly floating point formats in general have this __anomaly__...
(Just for the record.)
@ MadeInGermany... list ;; , yes, but it does not explain WHY, RudiC's reply just made it make sense even if there is only one command in the list.