0-9 and [[:digit:]] will match 1-10, but not more - bash

I'm trying to make a script, just for fun which creates a new User and Generates a Password for him.
Now I have to check if the user enters something stupid, instead of a digit.
function checkifcorrectnum() {
#Check if the User did something else than enter a number
#And check if the Number is absurdly big
case "$1" in
[[:digit:]] ) if [ "$1" -gt "255" ]; then echo "Too big!" ; else : ; fi ;;
*) echo "Please enter a number!"; exit ;;
esac
}
But when I run the script, and enter 1-9 it works, but anything higher wont

You are only matching for a single digit by just [[:digit:]]. The bash globbing cannot be used like Regex and match any token any number of times by operators like * or +. If you want to stick with your method and you know exactly how many digits you want to allow then use e.g. for 2 digits:
case "$1" in
[[:digit:]][[:digit:]])
If you are not sure then:
case "$1" in
[[:digit:]]*)
* expands to any number of character.
But i think you should look at Regex matching offered by bash by the =~ operator of [[, so your whole function can be rewritten as:
if [[ $1 =~ ^[[:digit:]]+$ ]]; then
[[ $1 -gt 255 ]] && echo "Too big!"
else
echo 'Please enter a number!' && exit
fi
Also as you are not doing anything if the number is <=255 so [[ $1 -gt 255 ]] && echo "Too big!" is enough.

Since it is easier to test if a string is not a number than to test if it is, I suggest inverting the order of the tests:
function checkifcorrectnum() {
case "$1" in
*[^[:digit:]]*) echo "Please enter a number";;
*) [ "$1" -gt "255" ] && echo "Too big!" ;;
esac;
}
The glob *[^[:digit:]]* matches if any character in $1 is not a digit.
Examples:
$ checkifcorrectnum 255
$ checkifcorrectnum 256
Too big!
$ checkifcorrectnum 25z
Please enter a number
As an aside, the keyword function is bash-only and generally not necessary. With the keyword removed, the code works not only in bash but also in any POSIX compatible shell.

Related

Syntax error in conditional expression and near `then' in Ubuntu terminal

I'm just learning terminal commands, I'm trying to create my own command, but I have the following problem
./myFile: line 10: syntax error in conditional expression
./myFile: line 11: syntax error near `then'
./myFile: line 11: ` then'
there is my code
#!/bin/bash
echo "please enter name of the folder"
read NAME1
if [[ $NAME1 ]]
then
echo "enter first number"
read NUM1
if [[ $NUM1 ]] && [[ $NUM1 -ge 0]]
then
echo "enter last number"
read NUM2
if [[ $NUM2 ]] && [[ $NUM2 -gt $NUM ]]
then
mkdir $NAME1{$NUM1..$NUM2}
else
echo"please enter last number to continue"
fi
else
echo "please enter first number to continue"
fi
else
echo "please enter name of the folder to continue"
fi
Firstly, the expression [[ $NAME1 ]] is not valid, or at least, does not do what you think it does. I believe you are trying to determine if the user actually typed in a string. To do this, you should either test for a non-empty string ([[ -n ${NAME1} ]]) or test for a string length greater than zero ([[ ${#NAME1} -gt 0 ]]). The same applies when you are testing $NUM1 and $NUM2.
Secondly, when dealing with user input, you should take care to avoid testing empty strings. This is best achieved by quoting your variables. For example: [[ "${NUM1}" -gt 0 ]].
Thirdly, spaces are important in tests. always leave a space after the [[ and before the ]].
In addition, $NUM (line 15) is not declared, so will evaluate to the empty string. This should be set somewhere earlier in the script.
There are many other areas in which this script could be improved (e.g. checking for numerical input, checking for valid folder names, etc. But the above changes should get you past the immediate errors.

Parameters work properly when remove their quoting

I am puzzled about the verbose of quoting in the script. Take an example from the instruction I followed:
min_val=1
max_val=100
int=50
if [[ "$int" =~ ^-?[0-9]+$ ]]; then
if [[ "$int" -ge "$min_val" && "$int" -le "$max_val" ]]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
Run it and come by:
$ bash test_integer3.sh
50 is within 1 to 100.
When I removed all the quoting in testing:
if [[ $int =~ ^-?[0-9]+$ ]]; then
if [[ $int -ge $min_val && $int -le $max_val ]]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
It's still working properly.
$ bash test_integer3.sh
50 is within 1 to 100.
Why should live with the habit of writing redundant quoting?
The real problem comes when you start to use [ command over [[ in your scripts. [[ is bash's improvement to the [ command. It has several enhancements that make it a better choice to write scripts targeting bash.
One such improvement would be that you no longer have to quote variables because [[ handles empty strings and strings with white-space more intuitively. For example consider your script written with [ for the un-quoted case and for discussions sake, one of your variables is empty
#!/usr/bin/env bash
min_val=
max_val=
int=50
if [[ $int =~ ^-?[0-9]+$ ]]; then
if [ $int -ge $min_val -a $int -le $max_val ]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
One thing to note is I've re-written the combined conditional using the -a syntax since [ does not support && operator within but could be combined using && as [ $int -ge $min_val ] && [ $int -le $max_val ]
You would see things going bad and seeing errors as below which means that one of the conditionals involving -le is gone wrong on seeing an empty string.
1_script.sh: line 7: [: -a: integer expression expected
50 is out of range.
whereas with same code for undefined variables and replacing the expression to use [[ would gracefully handle the empty strings to produce just an incorrect result as
50 is out of range.
So to sum it up, from the many advantages over using [[, the particular advantage on your case is to handle variables if there could be empty strings in your conditionals.
Quoting is used to to stop the word splitting. In the case above it is not necessary but consider a case like this: you have a directory and having theses files file1.txt, file2.txt, old.txt and file1 old.txt.
If you wish to remove the file file1 old.txt and run the command
rm file1 old.txt
then it will remove the file old.txt instead of what you expected.
In your piece of code you don't need quotes as you discovered. However, using quotes is considered "good practice" because unexpected things can happen without quotes. For example if you run the code with int equal to say "foo bar" you might get some strange results without quotes.
It is like the double and triple equals in JavaScript. You could probably get away with only double equals but some unexpected results might occur.

if condition inside function is not working as desired when function called with command line arguments inside find statement

#!/bin/bash
# Code to generate script usage
if [[ "$#" -ne 1 ]] && [[ "$#" -ne 2 ]]; then
flag=1;
elif ! [[ "$1" == "abcd" || "$1" == "dcba" ]]; then
echo "Invalid"
flag=1;
fi
while [ $# -gt 1 ]
do
case $2 in
'streams')
;;
*)
echo "unrecognised optional arg $2"; flag=1;
;;
esac
shift
done
if [ "$flag" == "1" ]; then
echo "Usage:"
exit
fi
function main {
arg1=$1
streams=$2
if [ "${streams}" == "streams" ]; then
echo entering here
else
echo entering there
fi
}
parent_dir=`pwd`
find $parent_dir -name "*" -type d | while read d; do
cd $denter code here
main $1 $2
done
Why the code does not enter "entering here" when script run with arguments "abcd" and "streams" ?
I feel that function having two arguments is causing the problem, code was working fine with one argument
Several things you might want to fix in your code, before attempts are made to find the specific problem. It is possible that it will disappear after modifying your script accordingly. If the problem is still alive, I'll edit my answer with a solution. If you decide to apply the following changes, please update your code in the question.
Consistent usage of either [[ or [. [[ is a Bash keyword similar to (but more powerful than) the [ command.
See
Bash FAQ 31
Tests And Conditionals
Unless you're writing for POSIX sh, I recommend [[.
Use (( for arithmetic expressions. ((...)) is an arithmetic command, which returns an exit status of 0 if the expression is nonzero, or 1 if the expression is zero. Also used as a synonym for let, if assignments are needed. See Arithmetic Expression.
Use the variable PWD instead of pwd. PWD is a builtin variable in all POSIX shells that contains the current working directory. pwd(1) is a POSIX utility that prints the name of the current working directory to stdout. Unless you're writing for some non-POSIX system, there is no reason to waste time executing pwd(1) rather than just using PWD.
The function keyword is not portable. I suggest you to avoid using it and simply write function_name() { your code here; } # Usage
$parent_dir is not double-quoted. "Double quote" every literal that contains spaces/metacharacters and every expansion: "$var", "$(command "$var")", "${array[#]}", "a & b". See
Quotes
Arguments
ShellCheck your code before uploading.
Replace the while condition logic with an if condition, so that shift is no longer required. Shift was the devil I was facing I found.
#!/bin/bash
# Code to generate script usage
if [[ "$#" -ne 1 ]] && [[ "$#" -ne 2 ]]; then
flag=1;
elif ! [[ "$1" == "abcd" || "$1" == "dcba" ]]; then
echo "Invalid"
flag=1;
fi
#while [[ $# -gt 1 ]]
#do
# case $2 in
# 'streams')
# ;;
# *)
# echo "unrecognised optional arg $2"; flag=1;
# ;;
# esac
# shift
#done
if [[ $2 == "streams" ]]; then
:
elif [[ (-z $2) ]]; then
:
else
echo "unrecognised optional arg $2"; flag=1;
fi
if [[ "$flag" == "1" ]]; then
echo "Usage:"
exit
fi
function main {
streams=$2
if [[ "${streams}" == "streams" ]]; then
echo entering here
else
echo entering there
fi
}
parent_dir=`pwd`
find $parent_dir -name "*" -type d | while read d; do
cd $d
main $1 $2
done

if arguments is equal to this string, define a variable like this string

I am doing some bash script and now I got one variable call source and one array called samples, like this:
source='country'
samples=(US Canada Mexico...)
as I want to expand the number of sources (and each source has its own samples) I tried to add some arguments to do this. I tried this:
source=""
samples=("")
if [ $1="country" ]; then
source="country"
samples="US Canada Mexico..."
else
echo "try again"
fi
but when I ran my script source countries.sh country it didn't work.
What am I doing wrong?
Don't forget about spaces:
source=""
samples=("")
if [ $1 = "country" ]; then
source="country"
samples="US Canada Mexico..."
else
echo "try again"
fi
You can use either "=" or "==" operators for string comparison in bash. The important factor is the spacing within the brackets. The proper method is for brackets to contain spacing within, and operators to contain spacing around. In some instances different combinations work; however, the following is intended to be a universal example.
if [ "$1" == "something" ]; then ## GOOD
if [ "$1" = "something" ]; then ## GOOD
if [ "$1"="something" ]; then ## BAD (operator spacing)
if ["$1" == "something"]; then ## BAD (bracket spacing)
Also, note double brackets are handled slightly differently compared to single brackets ...
if [[ $a == z* ]]; then # True if $a starts with a "z" (pattern matching).
if [[ $a == "z*" ]]; then # True if $a is equal to z* (literal matching).
if [ $a == z* ]; then # File globbing and word splitting take place.
if [ "$a" == "z*" ]; then # True if $a is equal to z* (literal matching).
It seems that you are looking to parse commandline arguments into your bash script. I have searched for this recently myself. I came across the following which I think will assist you in parsing the arguments:
http://rsalveti.wordpress.com/2007/04/03/bash-parsing-arguments-with-getopts/
I added the snippet below as a tl;dr
#using : after a switch variable means it requires some input (ie, t: requires something after t to validate while h requires nothing.
while getopts “ht:r:p:v” OPTION
do
case $OPTION in
h)
usage
exit 1
;;
t)
TEST=$OPTARG
;;
r)
SERVER=$OPTARG
;;
p)
PASSWD=$OPTARG
;;
v)
VERBOSE=1
;;
?)
usage
exit
;;
esac
done
if [[ -z $TEST ]] || [[ -z $SERVER ]] || [[ -z $PASSWD ]]
then
usage
exit 1
fi
./script.sh -t test -r server -p password -v

Is there an easy way to determine if user input is an integer in bash?

I am a new student to bash scripting, and I am stumped on an assignment question.
I was wondering if there is an easy way to determine whether a users' input is an integer or not. More specifically, if a user is prompted to input an integer, is there a quick check to validate?
One way is to check whether it contains non-number characters. You replace all digit characters with nothing and check for length -- if there's length there's non-digit characters.
if [[ -n ${input//[0-9]/} ]]; then
echo "Contains letters!"
fi
Another approach is to check whether the variable, evaluated in arithmetic context, is equal to itself. This is bash-specific
if [[ $((foo)) != $foo ]]; then
echo "Not just a number!"
fi
This is kind of a kludge, it's using -eq for something other then what it was intended, but it checks for an integer, if it doesn't find an int it returns both an error which you can toss to /dev/null and a value of false.
read input
if [[ $input ]] && [ $input -eq $input 2>/dev/null ]
then
echo "$input is an integer"
else
echo "$input is not an integer or not defined"
fi
You can test by using Regular expression
if ! [[ "$yournumber" =~ ^[0-9]+$ ]] ;
then exec >&2; echo "error: Not a number"; exit 1
fi
I found this post http://www.unix.com/shell-programming-scripting/21668-how-check-whether-string-number-not.html that talks about this.
If your input does not need to check if there is a +/- on the number, then you can do:
expr $num + 1 2> /dev/null
if [ $? = 0 ]
then
echo "Val was numeric"
else
echo "Val was non-numeric"
fi
Here is another way of doing it. It's probably a bit more elaborate than needed in most cases, but would handle decimals also. I had written the below code to get rounded number. It also checks for numeric input in the process.
#--- getRound -- Gives number rounded to nearest integer -----------------------
# usage: getRound <inputNumber>
#
# echos the rounded number
# Best to use it like:
# roundedNumber=`getRound $Number`
# check the return value ($?) and then process further
#
# Return Value:
# 2 - if <inputNumber> is not passed, or if more arguments are passed
# 3 - if <inputNumber> is not a positive number
# 0 - if <inputNumber> is successfully rounded
#
# Limitation: Cannot be used for negative numbers
#-------------------------------------------------------------------------------
getRound (){
if [ $# -ne 1 ]
then
exit 2
fi
#--- Check if input is a number
Input=$1
AB=`echo A${Input}B | tr -d [:digit:] | tr -d '.'`
if [ "${AB}" != "AB" ] #--- Allow only '.' and digit
then
exit 3
fi
DOTorNone=`echo ${Input} | tr -d [:digit:]` #--- Allow only one '.'
if [ "${DOTorNone}" != "" ] && [ "${DOTorNone}" != "." ]
then
exit 3
fi
echo $Input | awk '{print int($1+0.5)}' #--- Round to nearest integer
}
MyNumber=`getRound $1`
if [ $? -ne 0 ]
then
echo "Empty or invalid input passed"
else
echo "Rounded input: $MyNumber"
fi
This one works for me, handling empty input case.
if [ $input -eq $input 2>/dev/null -o $input -eq 0 2>/dev/null ]
then
echo Integer
else
echo Not an integer
fi

Resources