How do I pass in optional flags and parameters to bash script? - bash

I have a bash script that I pass parameters into (and access via $1). This parameter is a single command that must be processed (i.e. git pull, checkout dev, etc.).
I run my script like ./script_name git pull
Now, I want to add an optional flag to my script to do some other functionality. So if I call my script like ./script_name -t git pull it will have a different functionality from ./script_name git pull.
How do I access this new flag along with the parameters passed in. I've tried using getopts, but can't seem to make it work with the other non-flag parameters that are passed into the script.

Using getopts is indeed the way to go:
has_t_option=false
while getopts :ht opt; do
case $opt in
h) show_some_help; exit ;;
t) has_t_option=true ;;
:) echo "Missing argument for option -$OPTARG"; exit 1;;
\?) echo "Unknown option -$OPTARG"; exit 1;;
esac
done
# here's the key part: remove the parsed options from the positional params
shift $(( OPTIND - 1 ))
# now, $1=="git", $2=="pull"
if $has_t_option; then
do_something
else
do_something_else
fi

Related

Parse bash argument inside flag

I want to do a argument parser inside one argument.
Here is my launcher ./bin/kube/launch_market_quality_job.sh -e staging -j job_example
And here my script.
while [ ${#} -ne 0 ]; do
case "${1}" in
--environment | -e)
shift;
export ONE_ENVIRON=${1};
case $ONE_ENVIRON in
staging)
export REPOSITORY=<DTR>
;;
production)
export REPOSITORY=<DTR>
;;
*)
fail "You must provide the environment [staging|production]"
;;
esac
;;
--job | -j )
shift;
export JOB=${1}
case $JOB in
job_example_extra_args)
case "${1}" in
--name | -n )
export $NAME=${1}
[... extra args]
;;
*)
help
exit
;;
esac
shift
done
What I want to do is depending on "--j | -job" option, is to parse two more options depending if the jobs if one or another.
For example a normal job , called 'job_example' with the previous launcher it works correctly.
But if my job is 'job_example_extra_args' I need a new argument called 'name'.
`./bin/kube/launch_market_quality_job.sh -e staging -j job_example_extra_args --name "Jesus"`
I dont know how is the correct way, if this 'job_example_extra_args' is called, I want to obtain the flag correctly, and if its not included the script should fail or stop.
I want to add the flags options inside the --job flag, for activating it if the job is the one I want to.
I would catch all arguments and perform a check after all arguments are parsed, e.g.:
unset ONE_ENVIRON JOB NAME
while [ ${#} -ne 0 ]; do
case "${1}" in
--job | -j )
shift;
export JOB=${1}
;;
case "${1}" in
--name | -n )
shift
export NAME=${1}
;;
esac
shift
done
if [ "$JOB" = job_example_extra_args ] && [ -z "$NAME" ]; then
fail "You must provide a name for this job"
fi
This way, it does not matter if the user passes the --name NAME argument before or after the --job JOBNAME argument. All that matters is that the complete list of necessary arguments have been given at some point.
You could also report an error if the --name must be passed exclusively with the job_example_extra_args job:
if [ "$JOB" != job_example_extra_args ] && [ -n "$NAME" ]; then
fail "You must NOT provide a name for this job"
fi
PS. I am not showing --environment|-e nor * for clarity but there’s nothing wrong with them.
You could embed the secondary argument parsing inside the primary ("job") parse by inserting a complete parse loop inside each alternative job. But that's going to be hard to maintain, because the job-related logic becomes divorced from the job implementation.
Perhaps a better solution is to create an argument-parser+launcher for each job. Since you have already shifted the parsed arguments out of the way, you can just source an appropriate script using the . command. (Many shells allow source as a synonym for ..) Note that if you don't specify arguments after the filename in the . command, the existing arguments will not be altered so your unprocessed arguments will just get passed through. (For Posix shells, . might not even accept arguments following the filename.)

Why does my bash script think an argument is not present?

I am trying to write a simple bash script that would execute as follows
$ ./export.sh -n <my-file-name> -a <my-api-key>
I am using this as a way to pass some arguments at build time in a Go project.
A very simple version of the script is:
#!/bin/bash
while getopts n:a option
do
case "${option}"
in
n) FILENAME=${OPTARG};;
a) APIKEY=${OPTARG};;
esac
done
if [ -z "$FILENAME" ]
then
FILENAME=downloader
fi
if [ -z "$APIKEY" ]
then
echo "[ERROR] Missing API key"
exit 1
fi
cd src && go build -o ../build/${FILENAME}.exe downloader -ldflags "-X api.APIServiceKey="${APIKEY}
If the FILENAME does not exist I provide a default value, however if APIKEY is missing I would like to exist and show a message.
Running the script with all arguments however throws the error as if APIKEY was missing.
You are missing a colon in the getopts call. Since you expect an argument to -a, there must be a colon after it in the optstring: while getopts n:a: option
Quoting the getopts man page:
When the option requires an option-argument, the getopts utility shall
place it in the shell variable OPTARG. [...] If a character is followed
by a <colon>, the option shall be expected to have an argument which
should be supplied as a separate argument.

Pass commandline args into another script

I have couple of scripts which call into each other. However when I pass
Snippet from buid-and-run-node.sh
OPTIND=1 # Reset getopts in case it was changed in a previous run
while getopts "hn:c:f:s:" opt; do
case "$opt" in
h)
usage
exit 1
;;
n)
container_name=$OPTARG
;;
c)
test_command=$OPTARG
;;
s)
src=$OPTARG
;;
*)
usage
exit 1
;;
esac
done
$DIR/build-and-run.sh -n $container_name -c $test_command -s $src -f $DIR/../dockerfiles/dockerfile_node
Snippet from build-and-run.sh
OPTIND=1 # Reset getopts in case it was changed in a previous run
while getopts "hn:c:f:s:" opt; do
case "$opt" in
h)
usage
exit 1
;;
n)
container_name=$OPTARG
;;
c)
test_command=$OPTARG
;;
f)
dockerfile=$OPTARG
;;
s)
src=$OPTARG
;;
*)
usage
exit 1
;;
esac
done
I am calling it as such
build-and-run-node.sh -n test-page-helper -s ./ -c 'scripts/npm-publish.sh -r test/test-helpers.git -b patch'
with the intention that npm-publish.sh should run with the -r and -b parameters. However when I run the script I get
build-and-run.sh: illegal option -- r
which obviously means it is the build-and-run command that is consuming the -r. How do I avoid this?
You need double quotes around $test_command in buid-and-run-node.sh, otherwise that variable is being split on the white space and appears to contain arguments for buid-and-run.sh. Like this:
$DIR/build-and-run.sh -n $container_name -c "$test_command" -s $src -f $DIR/../dockerfiles/dockerfile_node
Further Info
As the comment below rightly points out, it's good practice to quote all variables in Bash, unless you know you want them off (for example, to enable shell globbing). It's also helpful, at least in cases where the variable name is part of a larger word, to use curly braces to delineate the variable name. This is to prevent later characters from being treated as part of the variable name if they're legal. So a better command call might look like:
"${DIR}/build-and-run.sh" -n "$container_name" -c "$test_command" -s "$src" -f "${DIR}/../dockerfiles/dockerfile_node"

Check if an argument is a path

I'm writing a script in bash. It will receive from 2 to 5 arguments. For example:
./foo.sh -n -v SomeString Type Directory
-n, -v and Directory are optional.
If script doesn't receive argument Directory it will search in current directory for a string.
Otherwise it will follow received path and search there. If this directory doesn't exist it will send a message.
The question is: Is there a way to check if the last arg is a path or not?
You can get last argument using variable reference:
numArgs=$#
lastArg="${!numArgs}"
# check if last argument is directory
if [[ -d "$lastArg" ]]; then
echo "it is a directory"
else
echo "it is not a directory"
fi
you can use this:
#!/bin/bash
if [[ -d ${!#} ]]
then
echo "DIR EXISTS"
else
echo "dosen't exists"
fi
First, use getopts to parse the options -n and -v (they will have to be used before any non-options, but that's not usually an issue).
while getopts nv opt; do
case $opt in
n) nflag=1 ;;
v) vflag=1 ;;
*) printf >&2 "Unrecognized option $opt\n"; exit 1 ;;
esac
done
shift $((OPTIND-1))
Now, you will have only your two required arguments, and possibly your third optional argument, in $#.
string_arg=$1
type_arg=$2
dir_arg=$3
if [ -d "$dir_arg" ]; then
# Do something with valid directory
fi
Note that this code will work in any POSIX-compliant shell, not just bash.

how to use getopt(s) as technique for passing in argument in bash

Can someone show me an example how to use getopts properly or any other technique that I would be able to pass in an argument? I am trying to write this in unix shell/bash. I am seeing there is getopt and getopts and not sure which is better to use. Eventually, I will build this out to add for more options.
In this case, I want to pass the filepath as input to the shell script and place a description in the case it wasn't entered correctly.
export TARGET_DIR="$filepath"
For example: (calling on the command line)
./mytest.sh -d /home/dev/inputfiles
Error msg or prompt for correct usage if running it this way:
./mytest.sh -d /home/dev/inputfiles/
As a user, I would be very annoyed with a program that gave me an error for providing a directory name with a trailing slash. You can just remove it if necessary.
A shell example with pretty complete error checking:
#!/bin/sh
usage () {
echo "usage: $0 -d dir_name"
echo any other helpful text
}
dirname=""
while getopts ":hd:" option; do
case "$option" in
d) dirname="$OPTARG" ;;
h) # it's always useful to provide some help
usage
exit 0
;;
:) echo "Error: -$OPTARG requires an argument"
usage
exit 1
;;
?) echo "Error: unknown option -$OPTARG"
usage
exit 1
;;
esac
done
if [ -z "$dirname" ]; then
echo "Error: you must specify a directory name using -d"
usage
exit 1
fi
if [ ! -d "$dirname" ]; then
echo "Error: the dir_name argument must be a directory
exit 1
fi
# strip any trailing slash from the dir_name value
dirname="${dirname%/}"
For getopts documentation, look in the bash manual
Correction to the ':)' line:
:) echo "Error: -$OPTARG requires an argument"
because if no value got provided after the flag, then OPTARG gets the name of the flag and flag gets set to ":" which in the above sample printed:
Error: -: requires an argument
which wasn't useful info.
Same applies to:
\?) echo "Error: unknown option -$OPTARG"
Thanks for this sample!

Resources