I took the code from this nice article: https://catonmat.net/tcp-port-scanner-in-bash
It uses the function scan. When I copy the code, it doesn't work for me.
I had similar issues with bash functions before and could not find the solution either.
I try to invoke the function this way directly in the script:
#!/bin/bash
scan() {
if [[ -z $1 || -z $2 ]]; then
echo "Usage: $0 <host> <port, ports, or port-range>"
return
fi
local host=$1
local ports=()
case $2 in
*-*)
IFS=- read start end <<< "$2"
for ((port=start; port <= end; port++)); do
ports+=($port)
done
;;
*,*)
IFS=, read -ra ports <<< "$2"
;;
*)
ports+=($2)
;;
esac
for port in "${ports[#]}"; do
alarm 1 "echo >/dev/tcp/$host/$port" &&
echo "port $port is open" ||
echo "port $port is closed"
done
}
scan
I also tried $(scan) - did not work as well.
The result - it does not want to scan anything ( I think there is something I should run in order to invoke the function - the code seems fine):
$ ./scan.sh google.com 22-99
Usage: ./scan.sh <host> <port, ports, or port-range>
$ ./scan.sh scan google.com 22-99
Usage: ./scan.sh <host> <port, ports, or port-range>
What am I missing? It is not the first time I've had a similar problem.
P.S. As was already suggested, scan "$1" "$2" did the job alright. However, now I encountered a similar issue when invoking this function from a different function:
function dosmth {
while test $# -gt 0; do
case "$1" in
-t)
shift
scan "$1" "$2"
shift
;;
*)
exit 1
;;
esac
done
}
dosmth $1
I also tried dosmth "$1" "$2" - the issue persists.
Right now dosmth function works alright but scan has the similar result as before it does not scan anything.
$ ./dosmt.sh -t google.com 80
Usage: -t ./dosmt.sh <host> <port, ports, or port-range>
My solution may help somebody - I changed to:
dosmth "$1" "$2" "$3"
If function run from a script, you need to pass args from script to the function
...
scan "$1" "$2"
Same goes when function run from another function, you have to pass args from "parent" function to "child" function.
fun1() {
echo "fun1 $1 $2"
}
fun2() {
fun1 "$1" "$2"
}
fun2 "$1" "$2"
Also it's a good practice to create vars with appropriate names from this args($1, $2, $3 ...) to use in functions.
#!/bin/bash
script=$0
host=$1
port_range=$2
ports=()
scan() {
if [[ -z $host || -z $port_range ]]; then
echo "Usage: $script <host> <port, ports, or port-range>"
return
fi
case $port_range in
*-*)
IFS=- read start end <<< "$port_range"
for ((port=start; port <= end; port++)); do
ports+=($port)
done
;;
*,*)
IFS=, read -ra ports <<< "$port_range"
;;
*)
ports+=($port_range)
;;
esac
for port in "${ports[#]}"; do
alarm 1 "echo >/dev/tcp/$host/$port" &&
echo "port $port is open" ||
echo "port $port is closed"
done
}
scan
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 am writing a bash wrapper for scp'ing into and from a certain host with a certain username, like:
johny#bonjour:~/bin$ cat scpphcl
#!/bin/bash
download=false
upload=false
local=""
remote=""
usage()
{
echo "Usage: $0 -d[-u] -l <LocalPath> -r <RemotePath>"
exit 1
}
while getopts "h?dul:r:" opt; do
case "$opt" in
h|\?)
usage
;;
d)
download=true
upload=false
;;
u)
download=false
upload=true
;;
l)
local=$OPTARG
;;
r)
remote=$OPTARG
;;
esac
done
if [[ -z $local || -z $remote ]]; then
echo "Need to provide local and remote path."
usage
fi
if $download; then
scp somebody#somehost:"$remote" $local
elif $upload; then
scp $local somebody#somehost:"$remote"
else
echo "Neither download nor upload?"
exit 1
fi
if [[ $? -ne 0 ]]; then
echo "Something wrong happened in the scp process."
exit 1
fi
exit 0
It works well with the usual filenames, but if there is any wildcard in the local filename field, it will not work right.
johny#bonjour:~/test$ scpphcl -u -l * -r /u/somebody/temp
Need to provide local and remote path.
Usage: /Users/johny/bin/scpphcl -d[-u] -l <LocalPath> -r <RemotePath>
There is a walkaround, using sinqle quotes around the local file argument if there is a wildcard in it:
johny#bonjour:~/test$ scpphcl -u -l '*' -r /u/somebody/temp
But even this walkaround will not work, if the command is issued outside the folder test:
johny#bonjour:~/test$ cd ..
johny#bonjour:~$ scpphcl -u -l 'test/*' -r /u/somebody/temp
This doesn't work and will hang in the scp process.
Any help in how to pass the wildcard in local filenames with the bash wrapper?
It's probably best not to require your users to quote wildcard patterns. I'd instead change the interface of your program to accept any number of local paths, after the option arguments:
echo "Usage: $0 [-d|-u] [-r <RemotePath>] <LocalPath>..."
When reading options, consume them with shift:
while getopts "h?dur:" opt; do
case "$opt" in
h|\?)
usage
exit 0
;;
d)
download=true
upload=false
;;
u)
download=false
upload=true
;;
r)
remote="$OPTARG"
;;
*)
usage >&2
exit 1
;;
esac
done
shift $((OPTIND-1))
Now the remaining positional arguments are the local filenames (and can be accessed with "$#" - note the all-important double-quotes there):
if test -z "$*" # no LocalPath arguments!
then usage >&2; exit 1
elif $download
then exec scp somebody#somehost:"$remote" "$#"
elif $upload
then exec scp "$#" somebody#somehost:"$remote"
fi
Latest Version
#!/bin/bash
set -e
shopt -s nocasematch
#vars
redbgbold='\e[1;97;41m'
resetcolor='\e[0m'
RegExFQDN='(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63}$)'
#functions
ask() {
local input
until
read -rp "$1 > " input >&2 || return 1
grep -q -P "$2" <<< "$input"
do
printf "ERROR - "${redbgbold}"\"$input\""${resetcolor}" is not a valid " >&2; sed "s/.*the //" <<< "$1" >&2
done
printf '%s\n' "$input"
}
#code
while [ -z $fqdn ]; do
fqdn=$(ask "Enter the FQDN" $RegExFQDN)
echo "FQDN is $fqdn"
done
The Question
I have a read line, and I want to take what the user entered and see if it matches my regex, if it matches we leave the loop, if it fails it prints an error and we do the loop again until we get a match. It looks redundant to me, and I assume there should be a better way but not sure what that should be.
Original Code
#!/bin/bash
set -e
shopt -s nocasematch
function RegexValidation() {
if [ "$2" = "fqdn" ]; then
if [ `echo $1 | grep -c -P '(?=^.{1,254}$)(^(?>(?!\d+\.)[a-z0-9_\-]{1,63}\.?)+(?:[a-z]{2,})$)'` == "0" ]; then
echo "ERROR - $1 is not a valid FQDN"
unset $!{1}
fi
fi
}
while [ -z $fqdn ]; do
read -e -r -p "Enter the Fully Qualified Domain Name > " fqdn
RegexValidation $fqdn fqdn
done
shopt -u nocasematch
any help is appreciated.
Update #1 - fixed formatting issues.
Update #2 - using that other guy's suggestions with a few additional tweaks
I would do basically the same thing, but split it differently to make it easier to reuse:
#!/bin/bash
set -e
ask() {
local input
until
read -rp "$1 > " input >&2 || return 1
grep -q -P "$2" <<< "$input"
do
echo "Invalid answer. Try again" >&2
done
printf '%s\n' "$input"
}
ask_fqdn() {
ask "$1" '(?=^.{1,254}$)(^(?>(?!\d+\.)[a-z0-9_\-]{1,63}\.?)+(?:[a-z]{2,})$)'
}
fqdn=$(ask_fqdn "Enter first FQDN")
echo "You wrote $fqdn"
fqdn=$(ask_fqdn "Enter second FQDN")
echo "This time it was $fqdn"
number=$(ask "And now a number because why not" '^\d+$')
echo "So $number"
Now you don't have to write a new loop every time you want new information, and you can easily ask for new things without modifying the existing functions.
Have the function return a status, which you can test with if in the loop.
And rather than use test to check the result of grep, just test it directly with if. grep returns a non-zero status if the input doesn't match.
function RegexValidation() {
if [ "$2" = "fqdn" ]; then
if ! echo "$1" | grep -q -P '(?=^.{1,254}$)(^(?>(?!\d+\.)[a-z0-9_\-]{1,63}\.?)+(?:[a-z]{2,})$)'; then
echo "ERROR - $1 is not a valid FQDN"
return 1
fi
return 0
fi
}
while :; do
read -e -r -p "Enter the Fully Qualified Domain Name > " fqdn
if RegexValidation "$fqdn" fqdn
then break
fi
done
Also, remember to quote your variables.
The script is:
#!/bin/bash
# Dynamic Menu Function
createmenu () {
select selected_option; do # in "$#" is the default
if [ 1 -le "$REPLY" ] && [ "$REPLY" -le $(($#)) ]; then
break;
else
echo "Please make a vaild selection (1-$#)."
fi
done
}
declare -a drives=();
# Load Menu by Line of Returned Command
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
# Display Menu and Prompt for Input
echo "Available Drives (Please select one):";
createmenu "${drives[#]}"
# Split Selected Option into Array and Display
drive=($(echo "${selected_option}"));
echo "Drive Id: ${drive[0]}";
echo "Serial Number: ${drive[1]}";
The older system doesn't have mapfile or readarray so I need to convert that line to some alternative that can read each line of the lsblk output into an array.
The line in question that creates the array is:
mapfile -t drives < <(lsblk --nodeps -o name,serial,size | grep "sd");
You can loop over your input and append to the array:
$ while IFS= read -r line; do arr+=("$line"); done < <(printf '%d\n' {0..5})
$ declare -p arr
declare -a arr='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'
Or, for your specific case:
while IFS= read -r line; do
drives+=("$line")
done < <(lsblk --nodeps -o name,serial,size | grep "sd")
See the BashFAQ/001 for an excellent explanation why IFS= read -r is a good idea: it makes sure that whitespace is conserved and backslash sequences not interpreted.
Here's the solution I came up with a while back. This is better because it provides a substitute function for older versions of Bash that don't support mapfile/readarray.
if ! type -t readarray >/dev/null; then
# Very minimal readarray implementation using read. Does NOT work with lines that contain double-quotes due to eval()
readarray() {
local cmd opt t v=MAPFILE
while [ -n "$1" ]; do
case "$1" in
-h|--help) echo "minimal substitute readarray for older bash"; exit; ;;
-r) shift; opt="$opt -r"; ;;
-t) shift; t=1; ;;
-u)
shift;
if [ -n "$1" ]; then
opt="$opt -u $1";
shift
fi
;;
*)
if [[ "$1" =~ ^[A-Za-z_]+$ ]]; then
v="$1"
shift
else
echo -en "${C_BOLD}${C_RED}Error: ${C_RESET}Unknown option: '$1'\n" 1>&2
exit
fi
;;
esac
done
cmd="read $opt"
eval "$v=()"
while IFS= eval "$cmd line"; do
line=$(echo "$line" | sed -e "s#\([\"\`]\)#\\\\\1#g" )
eval "${v}+=(\"$line\")"
done
}
fi
You don't have to change your code one bit. It just works!
readarray -t services -u < <(lsblk --nodeps -o name,serial,size | grep "sd")
For those playing along at home, this one aims to provide a mapfile that's feature-compliant with Bash 5, but still runs as far back as Bash 3.x:
#!/usr/bin/env bash
if ! (enable | grep -q 'enable mapfile'); then
function mapfile() {
local DELIM="${DELIM-$'\n'}"; opt_d() { DELIM="$1"; }
local COUNT="${COUNT-"0"}"; opt_n() { COUNT="$1"; }
local ORIGIN="${ORIGIN-"0"}"; opt_O() { ORIGIN="$1"; }
local SKIP="${SKIP-"0"}"; opt_s() { SKIP="$1"; }
local STRIP="${STRIP-"0"}"; opt_t() { STRIP=1; }
local FROM_FD="${FROM_FD-"0"}"; opt_u() { FROM_FD="$1"; }
local CALLBACK="${CALLBACK-}"; opt_C() { CALLBACK="$1"; }
local QUANTUM="${QUANTUM-"5000"}"; opt_c() { QUANTUM="$1"; }
unset OPTIND; local extra_args=()
while getopts ":d:n:O:s:tu:C:c:" opt; do
case "$opt" in
:) echo "${FUNCNAME[0]}: option '-$OPTARG' requires an argument" >&2; exit 1 ;;
\?) echo "${FUNCNAME[0]}: ignoring unknown argument '-$OPTARG'" >&2 ;;
?) "opt_${opt}" "$OPTARG" ;;
esac
done
shift "$((OPTIND - 1))"; set -- ${extra_args[#]+"${extra_args[#]}"} "$#"
local var="${1:-MAPFILE}"
### Bash 3.x doesn't have `declare -g` for "global" scope...
eval "$(printf "%q" "$var")=()" 2>/dev/null || { echo "${FUNCNAME[0]}: '$var': not a valid identifier" >&2; exit 1; }
local __skip="${SKIP:-0}" __counter="${ORIGIN:-0}" __count="${COUNT:-0}" __read="0"
### `while read; do...` has trouble when there's no final newline,
### and we use `$REPLY` rather than providing a variable to preserve
### leading/trailing whitespace...
while true; do
if read -d "$DELIM" -r <&"$FROM_FD"
then [[ ${STRIP:-0} -ge 1 ]] || REPLY="$REPLY$DELIM"
elif [[ -z $REPLY ]]; then break
fi
(( __skip-- <= 0 )) || continue
(( COUNT <= 0 || __count-- > 0 )) || break
### Yes, eval'ing untrusted content is insecure, but `mapfile` allows it...
if [[ -n $CALLBACK ]] && (( QUANTUM > 0 && ++__read % QUANTUM == 0 ))
then eval "$CALLBACK $__counter $(printf "%q" "$REPLY")"; fi
### Bash 3.x doesn't allow `printf -v foo[0]`...
### and `read -r foo[0]` mucks with whitespace
eval "${var}[$((__counter++))]=$(printf "%q" "$REPLY")"
done
}
### Alias `readarray` as well...
readarray() { mapfile "$#"; }
fi
if [[ -z ${PS1+YES} ]]; then
echo "'mapfile' should only be called as a shell function; try \"source ${BASH_SOURCE[0]##*/}\" first..." >&2
exit 1
fi
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
}