How do I control the user defined commands when executing a shell script in bash? - linux

I am creating a shell script which has its own commands (or options). In simpler terms, my command line for executing the script is (in the terminal): ./myscript.sh -o:1,2,3,4 or ./myscript.sh -e:2,3,4. Here is what the code (for the option part) looks like so far:
myscript.sh
for i
do
case "$i" in
"-o:"*|"--only:"*)
# check if 1 is not included (if it is then exit)
;;
"-e:"*|"--except:"*)
# check if 1 is excluded (if it is then exit)
;;
*)
echo -e "invalid option\nTry './audit.sh --help' for help"
exit
;;
esac
done
Explanation:
The purpose of this code is to sum up all the values that the user passes through the ./myscript.sh -o:1,2,3,4 or subtract the values ./myscript.sh -e:2,3,4. The o is the "only" option which adds up only the numbers passed to the script from this option. The e is the "exclude" option which excludes the values the user defines and subtracts those from 100.
I have a restriction that the number 1 must always be there (whether adding or subtracting). So I can't exclude the number 1. Here are some examples of illegal user input commands (./myscript.sh -e:1,2,3,4) and (./myscript.sh -o:2,3,4).
How do i check if the number 1 is always included in the only option and that the number 1 is not excluded from the exclude option? Thanks.
EDIT: The method I want to approach is using awk. Any suggestions?

Get the value to check
Add commas to the front and end
Check of ,1, is a substring
Implementation:
i="--only:3,4,5"
value=${i#*:} # Strip prefix up to colon
if [[ ,$value, == *,1,* ]] # Check if ,1, is a substring
then
echo "You have a 1 in your list."
else
echo "You're missing 1"
exit 1
fi
To invert the check for checking exclusion, you can use !=.

Related

Counting the number of inputs provided by user

Description: I have a script that begins with a warning/question posed to user. I need the user to answe with yes/y or no/n.
Issue: Though I have a conditional to make sure the user provides one of the following answers presented above I also need to make that only ONE input is provided by the user. I have attempted using
[ "$#" -eq 1 ] or [ $# -eq 1 ] neither of these coniditionals seems to work to solve the problem
here is what I have thus far:...
#!/bin/bash
#
# Descritpion: some Windows OS script
#
#
printf "(!)WARNING(!): 1. The this program is ONLY compatible with Windows operating systems. "
printf "2. You will NEED to be logged in as an ADMIN in order to fully make use of the '*****' script.\n"
printf "Would you like to continue? yes or no (y/n): "
#would it be cleaner to use "case" rather than a "while" w/ multiple conditionals? (01.19.2020)
read opt
while (true)
do
if [ $opt == yes ] || [ $opt == y ]
then
printf "continue. \n"
break
elif [ $opt == no ] || [ $opt == n ]
then
printf "OK, exiting the script! \n"
exit 0
#elif [ "$#" -ne 1 ]
#then
# "Too many arguments have been provided, please try again. \n"
# read opt
else
printf "The opition you provided is not recognized, please try again. \n"
read opt
fi
done
Not the ideal solution, but it includes an explanation of why $# is not working as you expect:
$# returns the number of arguments passed to a function. You have read 'opt' in as a user input - not the arguments to the function.
One solution could be to wrap your infinite while loop inside a function, and then pass $opt into it. It would then be the argument(s) to your function and you could use $# to count how many there are.
For details of the workings of many built-in functions in bash, you could try:
man bash
but I accept that there is a lot of information in there. You can search for the relevant words, normally using "/{searchstring}" (but you will probably need to "escape" the special characters, in this case: "/\$\#")
Take a look at read options
read: read [-ers] [-u fd] [-t timeout] [-p prompt] [-a array] [-n nchars] [-d delim] [name ...]
One line is read from the standard input, or from file descriptor FD if the
-u option is supplied, and the first word is assigned to the first NAME,
the second word to the second NAME, and so on, with leftover words assigned
to the last NAME. Only the characters found in $IFS are recognized as word
delimiters. If no NAMEs are supplied, the line read is stored in the REPLY
variable. If the -r option is given, this signifies `raw' input, and
backslash escaping is disabled. The -d option causes read to continue
until the first character of DELIM is read, rather than newline. If the -p
option is supplied, the string PROMPT is output without a trailing newline
before attempting to read. If -a is supplied, the words read are assigned
to sequential indices of ARRAY, starting at zero. If -e is supplied and
the shell is interactive, readline is used to obtain the line. If -n is
supplied with a non-zero NCHARS argument, read returns after NCHARS
characters have been read. The -s option causes input coming from a
terminal to not be echoed.
you would be able to simplify your script using some of them.
Read the input and parse it exactly. Decide, if you want to ignore spaces or not. You could even use a regex to make the input formatted as exactly as you want.
while (true) is an unnecessary use of subshell. Just while true.
if [ $opt == yes ] will not work if opt contains whitespaces, for example user inputs y e s. It will exit with a nonzero exit status and print an error message on standard error stream. As a rule, always quote your variable expansions. Always "$opt", never $opt. This is most probably the error you are asking for fixing.
Also the == is a bash extension. Use = for string comparison.
Cosmetics: [ $opt == yes ] || [ $opt == y ] unnecessary runs two processes in case the first one fails. Just run one process [ "$opt" = yes -o "$opt" = y ]. But the first one may be just more clear.
read opt ignores leading and trailing whitespaces and removes the \ slashes. Use IFS= read -r opt to read the whole line exactly as it is.
Use shellcheck.net to check your scripts.
The $# is the number of arguments passed to the script. It is unrelated to what read does. read saves the input to the variable.
So:
while true; do
if ! IFS= read -r opt; then
echo "ERROR: END OF INPUT!" >&2
exit 2
fi
case "$opt" in
y|yes) printf 'continue. \n'; break; ;;
n|no) printf 'OK, exiting the script! \n'; exit 1; ;;
*) printf 'The opition you provided is not recognized, please try again. \n'; ;;
esac
done

Scripts in sed - linux

I got the concept of bash, now, I found a site full of riddles for practising bash. I solve a couple of scripts (you should mention what do they do, what they are missing or so, depends on the question) and I bumped this script:
random_var="$(echo $1 | sed -e 's/[^[:alnum:]]//g')"
Correct me if I'm wrong about my basic assumptions on the following code:
$1 is the second argument that the script got (when the first is the script name)
There is a pipeline between the second argument and the sed script that removes all alpha numerics and... according to what I understand, this script can be "broken" by using a delimiter such as [/\]^$ and so ?
Now, there comes the difficulty (well, for me), the program gets an input from the user and, when the following script I just mention is found at a function returning true if the input is different than the result. I have no idea what is happening here, can someone enlighten me?
#!/bin/sh
func()
{
somevar="$(echo $1 | sed -e 's/[^[:alnum:]]//g')"
if [ "$somevar" != "$input" ] ; then
return 1
else
return 0
fi
}
# Sample usage of this function in a script
echo -n "Enter input: "
read input
if ! func "$input" ; then
echo "HELL NO"
exit 1
else
echo "YES!"
fi
exit 0
The script tests a string to see whether it contains any non-alphanumeric characters.
As Avinash has mentioned in the comments, the sed command removes all non-alphanumeric characters. Within the function, $input has the same value as it does in the calling scope, which is also the same as the first argument, $1. This is perhaps a little bit confusing...
If $somevar is different to $input (=$1), then this means that sed has changed the string in some way. Therefore, the string must contain at least one non-alphanumeric character.
If the function returns 1 (there were some non-alphanumeric characters in the input), then ! func is false, so the else branch will be executed and the script will return with an exit code of 0 (success). Otherwise, the script will return a non-zero exit code, indicating a failure.

ksh script + print argument content in shell script

I want to run the script.sh with one argument.
If the first argument = action then script.sh will print the action parameter - restart machine each 1 min
My example not work but please advice what need to fix in the script so I will print the $action parameter if argument is action.
Remark I not want to set the following solution - [[ $1 = action ]] && echo action "restart machine each 1 min
My example script:
#!/bin/ksh
action="restart machine each 1 min"
echo "action" ${$1}
Example how to run the script
./script.sh action
Expected results that I need to get :
action restart machine each 1 min
Well with pdksh this works:
echo "action" `eval echo '$'$1`
You want to use eval:
action="restart machine each 1 min"
eval echo $1 \$$1
Note that doing something like this is a huge security risk. Consider what happens if the user invokes the script with the first argument "; rm -rf /"
You can probably alleviate such problems with:
eval "echo '$1' \"\$$1\""
but really you're just asking for trouble (This last version will struggle if the first argument contains a double-quote, and a $() construct will permit an arbitrary command to be executed). It is much safer to simply use a case statement and check that the argument matches exactly a string that you are looking for. Or, at least check that the argument you are eval'ing does not contain any of the following characters: ;()$"'. It's probably safest to check that it only contains alphanumerics (a-zA-Z0-9)
It's been two years, but here's an example of using nameref (a.k.a. typeset -N).
It includes three consecutive tests for validity of the given argument.
Is an argument given?
Does the argument match a known variable? nameref checks this.
Does the target variable have a value set?
action='This is the value of $action'
word='This it the value of ${word}'
list='This is a list'
lie='This is a lie'
(
typeset name=${1:?Usage: script.sh varname} || exit
nameref arg1=${name} || exit
: ${arg1:?} || exit
echo "$name $arg1"
)

unable to set variable in case statement bash

I'm trying to set a variable based on a bunch of input conditions. Here's a small sample of the code:
#!/bin/bash
INSTANCE_SIZE=""
case "$1" in
"micro")
$INSTANCE_SIZE="t1.micro"
;;
"small")
$INSTANCE_SIZE="m1.small"
;;
esac
echo $INSTANCE_SIZE
When I run the script with the -ex switch and specify the proper argument:
+ case "$1" in
+ =m1.small
./provision: line 19: =m1.small: command not found
You need to remove the $ sign in the assignments - INSTANCE_SIZE="m1.small". With the dollar sign, $INSTANCE_SIZE gets substituted with its value and no assignment takes place - bash rather tries to execute the command that resulted from the interpolation.

What does this bash syntax mean? (Featuring: case, exec)

What is the purpose of this bash script? (It is a portion of a larger script.)
if [ $# -gt 0 ]
then
case $1 in
-*) ;;
*) exec $* ;;
esac
fi
A related question:
https://stackoverflow.com/questions/2046762/problem-with-metamap-inappropriate-ioctl-for-device
In English, line-by-line:
if the number of arguments is greater than 0
then
if the first argument...
starts with '-', do nothing
else, "exec" the arguments (run the entire set of arguments as a command replacing this process, not as a child process)
(end of case)
(end of if)
Not knowing any bash scripting I'd say this
looks for whether the number of arguments is larger than 0
if it is, it looks at the first argument
If it starts with - it does nothing
Otherwise it executes all arguments as a single command line
The case ... esac part is a switch statement. If $1 matches against -* (that is if it starts with -) the first case will be executed - and will do nothing. Otherwise (if $1 matches *, which depending on shell setting might exclude things starting with .) exec $* will be run.
Around that there is an if statement making sure that the switch is only executed if there actually are any parameters to be checked against (the parameter count is greater than zero).
It takes the first argument passed in and executes it with the remaining arguments I.E.:
./script.sh ls dir1 dir2
would act as if you had typed
ls dir1 dir2
If the first parameter placed on the command-line for this script is a file, not an option, then try to run it as an executable file or script.

Resources