I am new to bash scripting have patience.
I have this code where it scans ports of domains and shows them to the user and it works perfectly by it self but when i added functions to it gives me errors. What am I doing wrong here?
check_parms () {
case $# in
1) ports='1-1023'
host=$1 ;;
2) ports=$1
host=$2 ;;
*) echo 'Usage: portscan [port|range] host'
exit 1 ;;
esac
}
# check port range
check_ports () {
if [ "$(echo $ports | grep '^[1-9][0-9]*-[1-9][0-9]*$')" != "" ]; then
firstport=$(echo $ports | cut -d- -f1)
lastport=$(echo $ports | cut -d- -f2)
elif [ "$(echo $ports | grep '^[1-9][0-9]*$')" != "" ]; then
firstport=$ports
lastport=$ports
else
echo "$ports is an invalid port(s) value"
exit 2
fi
}
# check firstport > lastport
check_order () {
if [ $firstport -gt $lastport ]; then
echo $firstport is larger than $lastport
exit 3
fi
}
# check host value
check_host () {
regex='^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$'
if [ "$(echo $host | grep '[A-Za-z]')" != "" ]; then
response=$(host $host)
if [[ "$response" =~ "timed out" || "$response" =~ "not found" ]]; then
echo host $host not found
exit 4
fi
elif [[ ! $host =~ $regex ]]; then
echo $host is an invalid host address
exit 5
fi
}
# check if host is reachable using ping
check_ping () {
if [[ "$(ping -c 1 -W 2 -n $host)" =~ "0 received" ]]; then
echo $host is unreachable
exit 6
fi
}
# start the scan
do_scan () {
echo -n "Scanning "
for p in $(seq $firstport $lastport)
do
echo -n .
x=$((echo >/dev/tcp/$host/$p) >/dev/null 2>&1 && echo "$p open")
if [ "$x" != "" ]; then
y="${y}
$x"
fi
done
}
# show results of scan
show_results () {
echo -e "\n$y\n"
exit 0
}
check_parms $#
check_ports $ports
check_order $firstport $lastport
check_host $host
check_ping $host
do_scan $host $firstport $lastport
show_results
The way you're passing and using function arguments doesn't make sense. The one I see that's blatantly wrong is the first:
check_parms () {
case $# in
1) ports='1-1023'
host=$1 ;;
2) ports=$1
host=$2 ;;
*) echo 'Usage: portscan [port|range] host'
exit 1 ;;
esac
}
#... then in the main program:
check_parms $#
To understand the problem, suppose the main script is run with two arguments: "5-50" and "10.10.10.10". The line check_parms $# runs check_params and passes the number of arguments (2) as an argument to it. Fine so far. Then in check_params, it runs case $# in, but at this point we're inside check_params, so $# refers to the number of arguments passed to check_params, not the number passed to the main script. check_params was passed only one parameter (the number 2), so $# is 1, and it executes the 1) case, which sets ports to "1-1023" and host to "2". This is not what you wanted at all.
If you want check_params to be able to see the same parameters that were passed to the main script, you have to pass it those same parameters with check_params "$#".
The arguments passed to the other functions simply don't appear to be used; this won't necessarily cause trouble, but it makes for confusing code (and confusing code breeds bugs). Consider, for example the check_order function. You pass it $firstport and $lastport as arguments (meaning that within check_order, $1 is set to the value of $firstport and $2 is set to the value of $lastport), but then you don't use those arguments inside check_order; instead you use $firstport and $lastport directly, ignoring the parameters. Since they're the same thing, it doesn't really matter, but if someone later tried to use it for something else (check_order $currentport 1023) it would check $firstport and $lastport rather than what it looks like it should be checking.
Solution: change the functions to use their arguments rather than accessing global variables directly. (Or use the global variables explicitly and stop pretending to pass them as arguments; but globals are generally bad, so this is the worse option.) Something like this:
check_order () {
if [ $1 -gt $2 ]; then
echo $1 is larger than $2
exit 3
fi
}
Related
i have the following bash script contains multiple functions
#!/usr/bin/bash
#checks if the arguments is directory and extract the domain name from it
if [[ -d "$1" ]]; then
domain=$(echo "$1" | grep -iEo '[[:alnum:]-]+\.[a-z]+')
WORKING_DIR="$1"
else
domain="$1"
echo "it is domain name"
fi
example_fun1(){
ping -c 4 $domain
}
example_fun2(){
nslookup $domain
}
for x in "$#" ;do
example_fun1 $x
example_fun2 $x
done
and run as following
./script.sh ./pathtofolder/example.com/ ./pathtofolder/test.com/
Or
./script.sh example.com test.com
and working probably BUT i need to add more feature which is check if certain word based in arguments like fun1 it will execute function example_fun1 only
desired execution
./script.sh fun1 ./pathtofolder/example.com/ ./pathtofolder/test.com/
OR
./script.sh fun1 example.com test.com
Thanks
Try the following
#!/usr/bin/bash
function=""
if [[ $( echo $1 | grep fun1 ) ]]
then
function="example_fun1"
shift
elif [[ $( echo $1 | grep fun2 ) ]]
then
function="example_fun2"
shift
fi
#checks if the arguments is directory and extract the domain name from it
example_fun1(){
ping -c 4 $domain
}
example_fun2(){
nslookup $domain
}
if [[ "$function" != "" ]]
then
for input in "$#"; do
if [[ -d "$1" ]]; then
domain=$(echo "$1" | grep -iEo '[[:alnum:]-]+\.[a-z]+')
WORKING_DIR="$1"
else
domain="$1"
echo "it is domain name"
fi
"$function"
shift
done
else
for input in "$#"; do
if [[ -d "$1" ]]; then
domain=$(echo "$1" | grep -iEo '[[:alnum:]-]+\.[a-z]+')
WORKING_DIR="$1"
else
domain="$1"
echo "it is domain name"
fi
example_fun1
example_fun2
shift
done
fi
This way you can pass fun1 and execute only fun1
Or if you don't pass any of these for example both of them will be executed
Assign the first parameter to a variable, then use that when calling the function.
func="example_$1"
shift
for x in "$#"; do
"$func" "$x"
done
And your functions need to use their parameters, not a variable that's set in the main script:
example_fun1(){
ping -c 4 "$1"
}
example_fun2(){
nslookup "$1"
}
I'm trying to build a really simple TODO list with a bash script. It should allow user to add and remove a task and also see the entire list.
I've done it with the following script. But I've issues allowing a given task to take a whitespace as a string. For instance, if I'm adding a task with the command: ./programme_stack.sh add 1 start projet n1, it will only add a task with "start".
I've read a couple of things online, I know, I should double quote the variables but after trying this, it doesn't work. I must be missing something on the road.
Here is my script:
#!/bin/bash
TACHES=$HOME/.todo_list
# functions
function remove() {
res_remove=$(sed -n "$1p" $TACHES)
sed -i "$1d" $TACHES
}
function list() {
nl $TACHES
}
function add() {
if [ ""$(($(wc -l $TACHES | cut -d " " -f 1) + 1))"" == "$1" ]
then
echo "- $2" >> $TACHES
else
sed -i "$1i - $2" $TACHES
fi
echo "Task \"$2\" has been add to the index $1"
}
function isNumber() {
re='^[0-9]+$'
if ! [[ $# =~ $re ]] ; then
res_isNumber=true
else
res_isNumber=false
fi
}
# application
case $1 in
list)
list
;;
done)
shift
isNumber $#
if ! [[ "$res_isNumber" = false ]] ; then
echo "done must be followed by an index number"
else
nb_taches=$(wc -l $TACHES | cut -d " " -f 1)
if [ "$1" -ge 1 ] && [ "$1" -le $nb_taches ]; then
remove $1
echo "Well done! Task $i ($res_remove) is completed"
else
echo "this task doesn't exists"
fi
fi
;;
add)
shift
isNumber $1
if ! [[ "$res_isNumber" = false ]] ; then
echo "add must be followed by an index number"
else
index_max=$(($(wc -l $TACHES | cut -d " " -f 1) + 1))
if [ "$1" -ge 1 ] && [ "$1" -le $index_max ]; then
add $1 $2
else
echo "Idex must be between 1 and $index_max"
fi
fi
;;
*)
echo "./programme_stack.sh (list|add|done) [args]"
;;
esac
Can you guys see what I'm missing?
Many thanks!!
To get the script to support embedded spaces, 2 changes are needed
1) Accept embedded space - either
1A) Pass in the task name in quote script add nnn "say hello", OR
1B) Concatenate all the input parameters into single string.
2) Quote the task name to prevent it from being broken into individual words
In the code, implement 1B and 2
add)
...
if [ "$1" -ge 1 ] && [ "$1" -le $index_max ]; then
num=$1
shift
# Combine all remaining arguments
todo="$#"
add "$num" "$todo"
...
I want to check, if multiple variable are set or not, if set then only execute the script code, otherwise exit.
something like:
if [ ! $DB=="" && $HOST=="" && $DATE=="" ]; then
echo "you did not set any variable"
exit 1;
else
echo "You are good to go"
fi
You can use -z to test whether a variable is unset or empty:
if [[ -z $DB || -z $HOST || -z $DATE ]]; then
echo 'one or more variables are undefined'
exit 1
fi
echo "You are good to go"
As you have used the bash tag, I've used an extended test [[, which means that I don't need to use quotes around my variables. I'm assuming that you need all three variables to be defined in order to continue. The exit in the if branch means that the else is superfluous.
The standard way to do it in any POSIX-compliant shell would be like this:
if [ -z "$DB" ] || [ -z "$HOST" ] || [ -z "$DATE" ]; then
echo 'one or more variables are undefined'
exit 1
fi
The important differences here are that each variable check goes inside a separate test and that double quotes are used around each parameter expansion.
If you are ok with writing a function for this purpose, it can be pretty convenient.
This solution uses the ${!VAR_NAME} syntax to check whether the variable is empty and has the added benefit of telling you which variable names are empty.
check_vars()
{
var_names=("$#")
for var_name in "${var_names[#]}"; do
[ -z "${!var_name}" ] && echo "$var_name is unset." && var_unset=true
done
[ -n "$var_unset" ] && exit 1
return 0
}
# Usage for this case
check_vars DB HOST DATE
echo "You are good to go"
I wound up using variable-variables to loop through an easily managed HEREDOC list of variable names:
# Ensure non-empty values.
# Loop through HEREDOC, test variable-variable isn't blank.
while read var; do
[ -z "${!var}" ] && { echo "$var is empty or not set. Exiting.."; exit 1; }
done << EOF
KUBE_NAMESPACE
DOCKER_REGISTRY
DOCKER_DEPLOY_USER
DOCKER_DEPLOY_PASSWORD
DOCKER_DEPLOY_EMAIL
EOF
You can check it also by put the variables name in a file
DB=myDB
HOST=myDB
DATE=myDATE
then test them if currently empty or unset
#!/bin/bash
while read -r line; do
var=`echo $line | cut -d '=' -f1`
test=$(echo $var)
if [ -z "$(test)" ]; then
echo 'one or more variables are undefined'
exit 1
fi
done <var.txt
echo "You are good to go"
Nice solution from #joe.still !
improvement is to exit after checking all variables
i=0
while read var; do
[ -z "${!var}" ] && { echo "$var is empty or not set. Exiting.."; let i=i+1; }
done << EOF
KUBE_NAMESPACE
DOCKER_REGISTRY
DOCKER_DEPLOY_USER
DOCKER_DEPLOY_PASSWORD
DOCKER_DEPLOY_EMAIL
EOF
if [ $i -gt 0 ]; then
echo $i
echo "exiting"
exit 1
fi
Good Day Everyone.
I've personally used this method in my bash scripts. Verified works on bash 4.4 and later in Ubuntu, openSUSE, and ClearLinux.
Can RHEL|CentOS|Alma and Arch Based users let me know it it works fine for you?
( [ "$VAR1""$VAR2""$VAR3""$VAR4""$VAR5" ] && echo -e " Warning: StackIsNotClear" ) || { echo -e " GoodNews: StackIsClear"; }
I am trying to write a simple sh script that must be invoked with 2 arguments:
sh myscript.sh --user "some user" --fizz "buzz"
At the top of myscript.sh I have:
#!/bin/sh
# VALIDATION
# 1. Make sure there are 5 positional arguments (that $4 exists).
die () {
echo >&2 "$#"
exit 1
}
[ "$#" -eq 5 ] || die "5 arguments required, $# provided"
# 2. Make sure $1 is "-u" or "--user".
# 3. Make sure $3 is "-f" or "--fizz".
If validation fails, I'd like to print a simple usage message and then exit the script.
I think I have #1 correct (checking # of positional arguments), but have no clue how to implement #2 and #3. Ideas?
# 2. Make sure $1 is "-u" or "--user".
if ! [ "$1" = -u -o "$1" = --user ]; then
# Test failed. Send a message perhaps.
exit 1
fi
# 3. Make sure $3 is "-f" or "--fizz".
if ! [ "$3" = -f -o "$3" = --fizz ]; then
# Test failed. Send a message perhaps.
exit 1
fi
Other forms for testing a variable for two possible possible values:
[ ! "$var" = value1 -a ! "$var" = value2 ]
[ ! "$var" = value1 ] && [ ! "$var" = value2 ]
! [ "$var" = value1 && ! [ "$var" = value2 ]
For Bash and similarly syntaxed shells:
! [[ $var = value1 || $var = value2 ]]
[[ ! $var = value1 || ! $var = value2 ]]
Besides using negated conditions with if blocks, you can also have positive conditions with ||
true_condition || {
# Failed. Send a message perhaps.
exit 1
}
true_condition || exit 1
Of course && on the other hand would apply with negated conditions.
Using case statements:
case "$var" in
value1|value2)
# Valid.
;;
*)
# Failed.
exit 1
;;
esac
Manually:
if [ -z "$1" ];then
fi
if [ -z "$2" ];then
fi
if [ -z "$3" ];then
fi
...
Or check getopt
while getopts "uf" OPTION
do
case $OPTION in
u)
echo "-u"
;;
f)
echo "-f"
;;
esac
done
I've been banging my head on this one for a bit. Wanted to see what I'm doing wrong, and it's probably all me on this one. I'm pinging an IP, then when it goes down or up, it will send a notification. My issue is with getops. I'm using it to attempt to parse the email address and the ip. Neither of these are being parsed. How can I go about parsing my two variables using getopts I need to allow my script to run properly. Thank you in advance :)
#!/bin/bash
# Variable(s)
# --------
EMAIL_DOWN_SENT="0";
EMAIL_UP_SENT="0";
PING_FAILED="0";
PING_UP_AGAIN="0";
# FUNctions
# ---------
#
# Echo a string value that is passed
echo_text() {
echo -e >&2 "$#";
}
echo_help() {
echo_text "Usage: $(basename "$0") [-e] [EMAIL][-h] [-i] [IP]\n\nScript to ping ip address to see if it's up and send an alert on status changes.\n
-e Enter email: user#domain.tld
-h This help screen
-i IP to ping";
exit;
}
# Main body
# ---------
#
# Get command line options
# ------------------------
while getopts ":e:hi:" OPTIONS
do
case $OPTIONS in
e) EMAIL=$OPTARGS ;;
h|\?) echo_help ;;
i) IP= $OPTARGS | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'; if [ "$?" != "0" ]; then echo_text "Please enter a valid IP"; exit 1; fi; ;;
*) echo_text "$OPTARGS is invalid, try again";
exit 1 ;;
esac
shift $((OPTIND-1))
done
echo_text "Your email is: $EMAIL";
echo_text "Your ip to ping is: $IP";
# Ping me
# -------
trap "echo You hit ctrl-c, now exiting..; exit" SIGINT
while :
do
ping -i 2 -c 2 $IP > /dev/null
if [ $? -ne 0 ]; then
let PING_DOWN+=1
echo_text "Down! Abandon all hope who enter here...";
PING_FAILED="1";
# If greater than 3 cycles send an email...
if [[ $PING_DOWN -ge 3 && $EMAIL_DOWN_SENT -eq 0 ]]; then
echo "$IP Down!"|mutt -s "Ping Failed!" $EMAIL;
EMAIL_DOWN_SENT="1";
fi
else
let PING_UP+=1
echo_text "Up! Ahoy! Host alive!";
# If greater than 3 cycles send an email...
if [[ $PING_UP -ge 3 && $EMAIL_UP_SENT -eq 0 && $PING_FAILED -eq 1 ]]; then
echo "$IP up!"|mutt -s "Ping succeeded!" $EMAIL;
EMAIL_UP_SENT="1";
PING_UP_AGAIN="1";
fi
# Reset checks
if [ $PING_UP_AGAIN -eq 1 ]; then
PING_FAILED="0";
PING_UP="0";
PING_DOWN="0";
EMAIL_UP_SENT="0";
EMAIL_DOWN_SENT="0";
fi
fi
done
if [ ! `echo "$OPTARG" | grep -qE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'` ]; then
echo_text "Please enter a valid IP"
exit 1
fi
IP="$OPTARG"
There are two main things wrong in your script:
First, it's $OPTARG, not $OPTARGS.
Second,
IP= $OPTARGS | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}';
You cannot do that. Change the whole paragraph to something along the lines of
if ! echo "$OPTARG" | grep -qE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'; then
echo_text "Please enter a valid IP"
exit 1
fi
IP="$OPTARG"
It's also worth noting that, unless you are using an ancient bash, your shell should have a =~ operator (see here) that you can replace the external grep with:
When it is used, the string to the right of the operator is considered an extended regular expression and matched accordingly (as in regex3)). The return value is 0 if the string matches the pattern, and 1 otherwise.
In addition to the problems Adrian Frühwirth pointed out, you need to move the shift command to after the loop. The way you've written it, it's removing the script's arguments while getopts is still parsing them, which will confuse things completely.
while getopts ":e:hi:" OPTIONS
do
...
done
shift $((OPTIND-1)) # This must be after the "done"
Try to enclose $OPTARGS with "" :
e) EMAIL="$OPTARGS" ;;