Bash pulling elements from a function - bash

I'm curious to know if you can pull lines from a function in bash. Say I have this function:
error_fn()
{
echo "You need at least 2 command line arguments!"
echo "Program existing because you said you had a typo, please try agian"
echo "Sorry, one or both of the files that you entered was a directory, please try agian"
echo "Sorry, one or both files were not located, please try again"
}
Is there a way to pull the first echo statement (echo "You need at least 2 command line arguments!") from this array?
I have tried using:
error_fn $1
error_fn ("$1")
but this seems to just output all the echo statements in the function. Any ideas?

You could do your error_fn() similar to the following and pass the index value idx (zero-based, e.g. 0 - 3) as the argument to error_fn()to have the corresponding error output.
error_fn() {
array=( "You need at least 2 command line arguments!"
"Program existing because you said you had a typo, please try agian"
"Sorry, one or both of the files that you entered was a directory, please try agian"
"Sorry, one or both files were not located, please try again" )
idx=$(($1))
if [ '0' -le "$idx" -a "$idx" -le '3' ]
then
printf "error: %s\n" "${array[idx]}"
else
printf "error: function index '%d' out of range.\n" "$idx"
fi
}
Look it over and let me know if you have questions.
As for a general error/usage function, I generally prefer as much of the boiler-plate as possible being in a heredoc while having the ability to pass an error message as well through $1 for the funciton. Example:
usage() {
local ecode=${2:-0} ## set exit code '0' by default
test -n "$1" && printf "\n %s\n" "$1" >&2 ## print error msg (if any)
## heredoc of usage information
cat >&2 << USG
usage: ${0//*\//} _required_input_, etc..
This script ... (give description)
Options:
-h | --help program help (this file)
USG
exit $ecode;
}
It use would be to display usage information by default, display an error in addition to the usage if an error string is given as agrument 1, and finally to set the exit code for the program 0 by default but it will set any numeric argument as the exit code if passed as the second argument. Examples
[ -z "$1" ] && usage "warning: insufficient input." # General usage use.
[ -d "neededdir" ] || mkdir -p neededdir # error on exit and set ecode 2
[ -d "neededdir" } || usage "error: unable to create 'neededdir'." 2

The function is just another command creating output. You may parse it.
error_fn | head -n 1
However, if you rewrite the function to take an argument and to display an error message relating to that argument, it will probably do what you intend it to do:
function error_fn {
err="$1"
case "$err" in
EREFP) echo "Error: refining particles failed" >&2 ;;
ECYCE) echo "Error: number of cycles exceeded" >&2 ;;
EHUPM) echo "Error: hangup misdirected, dangling receiver" >&2 ;;
*) echo "Error: something's wrong (code:$err)" >&2 ;;
esac
}
if ! refine_particles; then
error_fn EREFP
else if ! hang_up_projections; then
error_fn EHUPM
fi

Related

showing instructions for command line inputs in a bash script when run without inputs or wrong inputs

I have created a bash script which takes 2 command line arguments. It works absolutely fine.
I want to go a step further, and want to show the types of arguments it takes. i.e. if we run my_script.bash --help it should tell the desired arguments in its order.
my_script.bash is as follows
#!/bin/bash
X="$1" ## Name
Y="$2" ## address
echo " Mr./Ms. $X lives in $Y ."
Since it takes two arguments name and address, I want to show these when this bash is executed without any inputs or number of inputs or using my_script.bash --help command.
ie executing ./my_script.bash or ./my_script.bash --help should show like below
$ ./my_script.bash
>>this script takes two arguments. please enter **Name** and **address**
Since these arguments are position specific so we cannot change the positions of Name and Address. It would be great if we could pass the arguments by defining --name --address.
$ ./my_script.bash --address Delhi --name Gupta
>> Mr./Ms. Gupta lives in Delhi .
Any help would be appreciated.
A basic option parsing loop uses while and case, and looks like this:
print_help ()
{
cat <<-EOF
${0##*/} - process name and address
--name NAME your name
--address ADDRESS your address
--help this help
EOF
}
die ()
{
echo "$#" >&2
exit 1
}
while [[ $# -gt 0 ]]; do
case $1 in
--help)
print_help
exit
;;
--name)
shift || die "$1: requires input"
name=$1
;;
--address)
shift || die "$1: requires input"
address=$1
;;
*)
die "$1: invalid argument"
esac
shift
done
The order of options doesn't matter.
You can test for [[ $1 == --help ]], but since your script has 2 required arguments, you could simply print the help whenever the number of arguments is not equal 2:
if (( $# != 2 ))
then
echo You have to provide 2 arguments 1>&2
exit 1
fi

bash script syntax error at function call and passing array as argument

I'm new to bash so assume that I don't understand everything in this simple script as I've been putting this together as of today with no prior experience with bash.
I get this error when I run test.sh:
command substitution: line 29: syntax error near unexpected token `$1,'
./f.sh: command substitution: line 29: `index_of($1, $urls))'
FILE: f.sh
#!/bin/bash
urls=( "example.com" "example2.com")
error_exit()
{
echo "$1" 1>&2
exit 1
}
index_of(){
needle=$1
haystack=$2
for i in "${!haystack[#]}"; do
if [[ "${haystack[$i]}" = "${needle}" ]]; then
echo "${i}"
fi
done
echo -1
}
validate_url_param(){
index=-2 #-2 as flag
if [ $# -eq 0 ]; then
error_exit "No url provided. Exiting"
else
index=$(index_of($1, $urls)) #ERROR POINTS TO THIS LINE
if [ $index -eq -1 ]; then
error_exit "Provided url not found in list. Exiting"
fi
fi
echo $index
}
FILE: test.sh
#!/bin/bash
. ./f.sh
index=$(validate_url_param "example.com")
echo $index
echo "${urls[0]}"
I've lost track of all of the tweaks I tried but google is failing me and I'm sure this is some basic stuff so... thanks in advance.
The immediate error, just like the error message tells you, is that shell functions (just like shell scripts) do not require or accept commas between their arguments or parentheses around the argument list. But there are several changes you could make to improve this code.
Here's a refactored version, with inlined comments.
#!/bin/bash
urls=("example.com" "example2.com")
error_exit()
{
# Include script name in error message; echo all parameters
echo "$0: $#" 1>&2
exit 1
}
# A function can't really accept an array. But it's easy to fix:
# make the first argument the needle, and the rest, the haystack.
# Also, mark variables as local
index_of(){
local needle=$1
shift
local i
for ((i=1; i<=$#; ++i)); do
if [[ "${!i}" = "${needle}" ]]; then
echo "${i}"
# Return when you found it
return 0
fi
done
# Don't echo anything on failure; just return false
return 1
}
validate_url_param(){
# global ${urls[#]} is still a bit of a wart
if [ $# -eq 0 ]; then
error_exit "No url provided. Exiting"
else
if ! index_of "$1" "${urls[#]}"; then
error_exit "Provided url not found in list. Exiting"
fi
fi
}
# Just run the function from within the script itself
validate_url_param "example.com"
echo "${urls[0]}"
Notice how the validate_url_param function doesn't capture the output from the function it is calling. index_of simply prints the result to standard output and that's fine, just let that happen and don't intervene. The exit code tells us whether it succeeded or not.
However, reading the URLs into memory is often not useful or necessary. Perhaps you are simply looking for
grep -Fx example.com urls.txt

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"

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

In bash, is there an equivalent of die "error msg"

In perl, you can exit with an error msg with die "some msg". Is there an equivalent single command in bash? Right now, I'm achieving this using commands: echo "some msg" && exit 1
You can roll your own easily enough:
die() { echo "$*" 1>&2 ; exit 1; }
...
die "Kaboom"
Here's what I'm using. It's too small to put in a library so I must have typed it hundreds of times ...
warn () {
echo "$0:" "$#" >&2
}
die () {
rc=$1
shift
warn "$#"
exit $rc
}
Usage: die 127 "Syntax error"
This is a very close function to perl's "die" (but with function name):
function die
{
local message=$1
[ -z "$message" ] && message="Died"
echo "$message at ${BASH_SOURCE[1]}:${FUNCNAME[1]} line ${BASH_LINENO[0]}." >&2
exit 1
}
And bash way of dying if built-in function is failed (with function name)
function die
{
local message=$1
[ -z "$message" ] && message="Died"
echo "${BASH_SOURCE[1]}: line ${BASH_LINENO[0]}: ${FUNCNAME[1]}: $message." >&2
exit 1
}
So, Bash is keeping all needed info in several environment variables:
LINENO - current executed line number
FUNCNAME - call stack of functions, first element (index 0) is current function, second (index 1) is function that called current function
BASH_LINENO - call stack of line numbers, where corresponding FUNCNAME was called
BASH_SOURCE - array of source file, where corresponfing FUNCNAME is stored
Yep, that's pretty much how you do it.
You might use a semicolon or newline instead of &&, since you want to exit whether or not echo succeeds (though I'm not sure what would make it fail).
Programming in a shell means using lots of little commands (some built-in commands, some tiny programs) that do one thing well and connecting them with file redirection, exit code logic and other glue.
It may seem weird if you're used to languages where everything is done using functions or methods, but you get used to it.
# echo pass params and print them to a log file
wlog(){
# check terminal if exists echo
test -t 1 && echo "`date +%Y.%m.%d-%H:%M:%S` [$$] $*"
# check LogFile and
test -z $LogFile || {
echo "`date +%Y.%m.%d-%H:%M:%S` [$$] $*" >> $LogFile
} #eof test
}
# eof function wlog
# exit with passed status and message
Exit(){
ExitStatus=0
case $1 in
[0-9]) ExitStatus="$1"; shift 1;;
esac
Msg="$*"
test "$ExitStatus" = "0" || Msg=" ERROR: $Msg : $#"
wlog " $Msg"
exit $ExitStatus
}
#eof function Exit

Resources