Check if the parameter of a shell command exists - shell

In my program, I have to check whether a command given as a input by a user exists or not and if it exists, program needs to check if the parameters of that command are correct.
For example:
ls ( is correct)
-al (is correct)
do the watch
and if I do this:
ls (is correct)
-kala (not correct)
don't do the watch.
How I can do this? Here is my script:
while true
do
echo "Insert the command"
read comm
if [ "$(type -t $comm)" != "" ]; then
echo "Insert the parameters of the command ";
read par;
echo "Insert the time of watch";
read time;
if [ $t -le 0 ]; then
echo "Value not correct";
else
clear;
while true
do
echo "$comm"
date
echo ""
$comm $par
sleep $((time))
clear
done
fi;
else
echo "Command not found, retry.";
echo "";
fi
done

You can replace the command invocation with this:
if ! $comm $par; then
exit 1
fi
to make it stop after an error. Also there is already a tool called watch but I think you already know this.

Related

How to process basic commandline arguments in Bash?

So I started today taking a look at scripting using vim and I'm just so very lost and was looking for some help in a few areas.
For my first project,I want to process a file as a command line argument, and if a file isn't included when the user executes this script, then a usage message should be displayed, followed by exiting the program.
I have no clue where to even start with that, will I need and if ... then statement, or what?
Save vim for later and try to learn one thing at a time. A simpler text editor is called nano.
Now, as far as checking for a file as an argument, and showing a usage message otherwise, this is a typical pattern:
PROGNAME="$0"
function show_usage()
{
echo "Usage: ${PROGNAME} <filename>" >&2
echo "..." >&2
exit 1
}
if [[ $# -lt 1 ]]; then
show_usage
fi
echo "Contents of ${1}:"
cat "$1"
Let's break this down.
PROGNAME="$0"
$0 is the name of the script, as it was called on the command line.
function show_usage()
{
echo "Usage: ${PROGNAME} <filename>" >&2
echo "..." >&2
exit 1
}
This is the function that prints the "usage" message and exits with a failure status code. 0 is success, anything other than 0 is a failure. Note that we redirect our echo to &2--this prints the usage message on Standard Error rather than Standard Output.
if [[ $# -lt 1 ]]; then
show_usage
fi
$# is the number of arguments passed to the script. If that number is less than 1, print the usage message and exit.
echo "Contents of ${1}:"
cat "$1"
$1 is out filename--the first argument of the script. We can do whatever processing we want to here, with $1 being the filename. Hope this helps!
i think you're asking how to write a bash script that requires a file as a command-line argument, and exits with a usage message if there's a problem with that:
#!/bin/bash
# check if user provided exactly one command-line argument:
if [ $# -ne 1 ]; then
echo "Usage: `basename "$0"` file"
exit 1
# now check if the provided argument corresponds to a real file
elif [ ! -f "$1" ]; then
echo "Error: couldn't find $1."
exit 1
fi
# do things with the file...
stat "$1"
head "$1"
tail "$1"
grep 'xyz' "$1"

How to execute a file that is located in $PATH

I am trying to execute a hallo_word.sh that is stored at ~/bin from this script that is stored at my ~/Desktop. I have made both scripts executable. But all the time I get the problem message. Any ideas?
#!/bin/sh
clear
dir="$PATH"
read -p "which file you want to execute" fl
echo ""
for fl in $dir
do
if [ -x "$fl" ]
then
echo "executing=====>"
./$fl
else
echo "Problem"
fi
done
This line has two problems:
for fl in $dir
$PATH is colon separated, but for expects whitespace separated values. You can change that by setting the IFS variable. This changes the FIELD SEPARATOR used by tools like for and awk.
$fl contains the name of the file you want to execute, but you overwrite its value with the contents of $dir.
Fixed:
#!/bin/sh
clear
read -p "which file you want to execute" file
echo
IFS=:
for dir in $PATH ; do
if [ -x "$dir/$file" ]
then
echo "executing $dir/$file"
exec "$dir/$file"
fi
done
echo "Problem"
You could also be lazy and let a subshell handle it.
PATH=(whatever) bash command -v my_command
if [ $? -ne 0 ]; then
# Problem, could not be found.
else
# No problem
fi
There is no need to over-complicate things.
command(1) is a builtin command that allows you to check if a command exists.
The PATH value contains all the directories in which executable files can be run without explicit qualification. So you can just call the command directly.
#!/bin/sh
clear
# r for raw input, e to use readline, add a space for clarity
read -rep "Which file you want to execute? " fl || exit 1
echo ""
"$fl" || { echo "Problem" ; exit 1 ; }
I quote the name as it could have spaces.
To test if the command exists before execution use type -p
#!/bin/sh
clear
# r for raw input, e to use readline, add a space for clarity
read -rep "Which file you want to execute? " fl || exit 1
echo ""
type -p "$fq" >/dev/null || exit 1
"$fl" || { echo "Problem" ; exit 1 ; }

Shell Script not reading input

I have written a script that backs up and restores files. I have a problem in that when the user enters '2' for a restore the program says that this is an invalid input, all other options work fine. I feel it is something small that I have missed but I cant fix it
Update and Restore Script
#!/bin/bash
ROOT="/Users/Rory/Documents"
ROOT_EXCLUDE="--exclude=/dev --exclude=/proc --exclude=/sys --exclude=/temp --exclude=/run --exlucde=/mnt --exlcude=/media --exlude=/backup2.tgz"
DESTIN="/Users/Rory/test/"
BACKUP="backup2.tgz"
CREATE="/dev /proc /sys /temp /run /mnt /media "
if [ "$USER" != "root" ]; then
echo "You are not the root user"
echo "To use backup please use: sudo backup"
exit
fi
clear
echo "************************************************"
echo "********* Backup Menu **************************"
echo "************************************************"
OPTIONS="BACKUP RESTORE DESTINATION EXIT"
LIST="1)BACKUP 2)RESTORE 3)DESTINATION 4)EXIT"
select opt in $OPTIONS; do
if [ "$opt" = "EXIT" ]; then
echo "GOODBYE!"
sleep 3
clear
exit
elif [ "$opt" = "BACKUP" ]; then
echo "BACKING UP FILES..."
sleep 2
tar cvpfz $DESTIN/backup.`date +%d%m%y_%k:%M`.tgz $ROOT $ROOT_EXCLUDE_DIRS
echo "BACKUP COMPLETE"
sleep 2
exit
elif [ "$opt" = "RESTORE" ]; then
echo "RESTOTING FILES..."
sleep 2
tar xvpfz $BACKUP_FILE -C /
sleep2
echo "RESTORE COMPLETE..."
if [[ -e "/proc" ]]; then
echo "$CREATE_DIRS allready exists! "
else
mkdir $CREATE_DIRS
echo "$CREATE_DIRS are created! "
fi
exit
elif [ "$opt" = "DESTINATION" ]; then
echo "CURRENT DESTINATION: $DEST_DIR/backup.`date +%d/%m/%y_%k:%M`.tgz "
echo "TO CHANGE ENTER THE NEW DESTINATION..."
echo "TO LEAVE IT AS IS JUST PRESS ENTER..."
read NEW_DESTIN
#IF GREATER THEN 0 ASSIGN NEW DESTINATION
if [ ${#NEW_DESTIN} -gt 0 ]; then
DESTIN = "$NEW_DESTIN"
fi
clear
echo $BANNER1
echo $BANNER2
echo $BANNER3
echo $LIST
else
clear
echo "BAD INPUT!"
echo "ENTER 1 , 2, 3 or 4.."
echo $LIST
fi
done
Except where you missed the ending quote where you set ROOT_EXCLUDE (line #4), it looks okay to me. I take it the missing quote is a transcription error or your program wouldn't really work at all.
I've tried out the program and it seems to work.
A debugging trick is to put set -xv to turn on debugging in your script and set +xv to turn it off. The -x means to print out the line before executing, and the -v means to print out the line once the shell interpolates the line.
I'm sure that you'll immediately see the issue once you have set -xv in your program.
As part of this, you can set PS4 to the line prompt to print when the debugging information is printed. I like setting PS4 like this:
export PS4="[\$LINENO]> "
This way, the line prompt prints out the line it's executing which is nice.
In your case, I would put set -xv right before you set OPTIONS and then at the very end of the program. This way, you can see the if comparisons and maybe spot your issue.
export PS4="[\$LINENO]> "
set -xv
OPTIONS="BACKUP RESTORE DESTINATION EXIT"
LIST="1)BACKUP 2)RESTORE 3)DESTINATION 4)EXIT"
select opt in $OPTIONS; do
if [ "$opt" = "EXIT" ]; then
echo "GOODBYE!"
set +xv
By the way, it's better to use double square brackets like [[ ... ]] for testing rather than the single square brackets like [ ... ]. This has to do with the way the shell interpolates the values in the test.
The [ ... ] is an alias to the built in test command. The shell interpolates the line as is and the entire line is executed.
The [[ ... ]] are a compound statement where the shell will interpolate variables, but not the entire line. The line is kept as whole:
foo="one thing"
bar="another thing"
This will work:
if [ "$foo" = "$bar" ]
then
echo "Foo and bar are the same"
fi
This won't:
if [ $foo = $bar ]
then
echo "Foo and bar are the same"
fi
The shell interpolates the line as is:
if [ one thing = another thing ]
And this is the same as:
if test one thing = another thing
The test command looks at the first item to see if it's a standard test, or assumes three items and the second item is a comparison. In this case, neither is true.
However, this will work:
if [[ $foo = $bar ]] # Quotes aren't needed
then
echo "Foo and bar are the same"
fi
With the [[ ... ]] being a compound command, the $foo and $bar are replaced with their values, but their positions are kept. Thus, the = is recognized as a comparison operator.
Using [[ ... ]] instead of [ ... ] has solved a lot of hard to find shell scripting bugs I have.

unix construct "or" condition on a filename

I have a shell script where I pass (2) parameters, one to pass a dbname, the other to call one of (2) filenames. I want to check if either filename exists, then proceed with calling that script, else exit because the user can enter the wrong string and construct my_foo.sql which I don't want. I don't think I have the condition for setting "or" correctly since putting the correct param still produces error. Is there a better way to write this?
Here is what I have so far.
#/usr/bin/ksh
if [ $# != 2 ]; then
echo "Usage: test.sh <dbname> <test|live>" 2>&1
exit 1
fi
# Check actual file name
CHKSCRIPT1=/tmp/my_test.sql;
CHKSCRIPT2=/tmp/my_live.sql;
if [ -f "CHKSCRIPT1" ] || [ -f "CHKSCRIPT2" ]
then
/bin/sqlplus -s foo/bar #/my_$2.sql
else
echo "Correct sql script does not exist. Enter test or live"
exit 1
fi
Your issue is that you're not referencing your variables correctly:
if [ -f "$CHKSCRIPT1" ] || [ -f "$CHKSCRIPT2" ]
...
fi
edit: Per #chepner, you shouldn't use -o
In addition to the problem you had with expanding the parameters, you should separate what the user types from what files need to exist. If the user enters "live", the only thing that matters is whether or not /tmp/my_live.sql exists. If the user enters "injection_attack", your script should not execute /tmp/my_injection_attack.sql (which presumably was created without your knowledge). The right thing to do is to first verify that a valid command was entered, then check if the appropriate file exists.
if [ $# != 2 ]; then
echo "Usage: test.sh <dbname> <test|live>" 2>&1
exit 1
fi
case $2 in
test|live)
filename="/tmp/my_{$2}.sql"
;;
*) echo "Must enter test or live"
exit 1
;;
esac
if [ -f "$filename" ]; then
/bin/sqlplus -s foo/bar #/my_$2.sql
else
echo "SQL script $filename does not exist."
exit 1
fi

SHELL general function for action state

How to make a code bellow as a general function to be used entire script in bash:
if [[ $? = 0 ]]; then
echo "success " >> $log
else echo "failed" >> $log
fi
You might write a wrapper for command execution:
function exec_cmd {
$#
if [[ $? = 0 ]]; then
echo "success " >> $log
else
echo "failed" >> $log
fi
}
And then execute commands in your script using the function:
exec_cmd command1 arg1 arg2 ...
exec_cmd command2 arg1 arg2 ...
...
If you don't want to wrap the original calls you could use an explicit call, like the following
function check_success {
if [[ $? = 0 ]]; then
echo "success " >> $log
else echo "failed" >> $log
fi
}
ls && check_success
ls non-existant
check_success
There's no really clean way to do that. This is clean and might be good enough?
PS4='($?)[$LINENO]'
exec 2>>"$log"
That will show every command run in the log, and each entry will start with the exit code of the previous command...
You could put this in .bashrc and call it whenever
function log_status { [ $? == 0 ] && echo success>>/tmp/status || echo fail>>/tmp/status }
If you want it after every command you could make the prompt write to the log (note the original PS1 value is appended).
export PS1="\$([ \$? == 0 ] && echo success>>/tmp/status || echo fail>>/tmp/status)$PS1"
(I'm not experienced with this, perhaps PROMPT_COMMAND is a more appropriate place to put it)
Or even get more fancy and see the result with colours.
I guess you could also play with getting the last executed command:
How do I get "previous executed command" in a bash script?
Get name of last run program in Bash
BASH: echoing the last command run

Resources