getopts: unable to identify arguments - linux

This is the script I tried:
#!/bin/bash
while getopts ":u:p:" option; do
case $option in
u) USER=$OPTARG;;
p) PASS=$OPTARG;;
\?) echo "Invalid Option: $OPTARG"
exit 1;;
:) echo "Please provide an argument for $OPTARG!"
exit 1;;
esac
done
echo "Username/Password: $USER/$PASS"
If command for running the script is:
./test9.sh -u test -p -a
Then I am getting an output:
Username/Password: test/-a
-a is an invalid argument but the script is taking -a as password. I would like to display a message Please enter a password and exit the script. Please help me in fixing this.

There are three kinds of parameters: options, option arguments, and positional parameters. If a parameter is an option that requires an argument, then the next parameter will be treated as an an argument no matter what. It may start with a dash or even coincide with a valid option, it will still be treated as an option argument.
If your program wants to reject arguments that start with a dash, you need to program it yourself. Passwords that start with a dash are perfectly legitimate; a program that checks passwords must not reject them.
Option that accept optional arguments are extremely confusing and non-standard. Getopt in general doesn't support them. There's a GNU extension for that, but don't use it.
TL;DR there's nothing to fix, your script is fine.

I haven't tested your script, but I think that if you use getopt instead of getopts you'll get the result you expect, an error because -a is not a valid option.

Related

Dynamically generate command in bash

I want to dynamically generate pretty long bash command depending on the command line options. Here is what I tried:
CONFIG_PATH=""
#Reading CONFIG_PATH from getopts if supplied
SOME_OPT=""
if [ ! -z "$CONFIG_PATH" ]; then
SOME_OPT="-v -s -cp $CONFIG_PATH"
fi
some_bash_command $SOME_OPT
The point here is that I want to pass 0 arguments to the some_bash_command if no arguments were passed to the script. In case there were some arguments I want to pass them.
It works fine, but the problem is that this approach looks rather unnatural to me.
What would be a better yet practical way to do this?
Your approach is more-or-less the standard one; the only significant improvement that I'd recommend is to use an array, so that you can properly quote the arguments. (Otherwise your command can horribly misbehave if any of the arguments happen to include special characters such as spaces or asterisks.)
So:
SOME_OPT=()
if [ ! -z "$CONFIG_PATH" ]; then
SOME_OPT=(-v -s -cp "$CONFIG_PATH")
fi
some_bash_command "${SOME_OPT[#]}"

BASH getopts Multiple Scripts with Same Options

I have a series of BASH scripts.
I am using getopts to parse arguments from the cmd line (although open to alternatives).
There are a series of common options to these scripts call this options set A
ie queue, ncores etc.
Each script then has a series of extra options ie set B1,B2,B3.
What I want is for script
"1 to be able to take options A+B1"
"2 to be able to take options A+B2"
"3 to be able to take options A+B2"
But I want to be able to store the code for options A in a central location (library/function) with having to write out in each script.
What I want is a way to insert generic code in getopts. Or alternatively a way to run getopts twice.
In fact I've done this by having getopts as a function which is sourced.
But the problem is I cant get the unrecognised option to work them.
I guess one way would be to remove the arguements from options A from the string before passing to a getopts for B1, B2 , B3 etc ?
Thanks Roger
That's a very nice question, to answer which we need to have a good understanding of how getopts works.
The key point here is that getopts is designed to iterate over the supplied arguments in a single loop. Thus, the solution to the question is to split the loop between different files rather then running the command twice:
#!/usr/bin/env bash
# File_1
getopts_common() {
builtin getopts ":ab:${1}" ${2} ${#:3} || return 1
case ${!2} in
'a')
echo 'a triggered'
continue
;;
'b')
echo "b argument supplied -- ${OPTARG}"
continue
;;
':')
echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
exit 1
;;
esac
}
#!/usr/bin/env bash
# File_2
# source "File_1"
while getopts_common 'xy:' OPTKEY ${#}; do
case ${OPTKEY} in
'x')
echo 'x triggered'
;;
'y')
echo "y argument supplied -- ${OPTARG}"
;;
'?')
echo "INVALID OPTION -- ${OPTARG}" >&2
exit 1
;;
':')
echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
exit 1
;;
*)
echo "UNIMPLEMENTED OPTION -- ${OPTKEY}" >&2
exit 1
;;
esac
done
Implementation notes
We start with File_2 since that's where the execution of the script starts:
Instead of invoking getopts directly, we call it via it's proxy: getopts_common, which is responsible for processing all common option.
getopts_common function is invoked with:
A string that defines which options to expect, and where to expect their arguments. This string only covers options defined in File_2.
The name of the shell-variable to use for option reporting.
A list of the command line arguments. (This simplifies accessing them from inside getopts_common function.)
Moving on to the sourced file (File_1) we need to bear in mind that getopts_common function runs inside the while loop defined in File_2:
getopts returns false if there is nothing left to parse, || return 1 bit insures that getopts_common function does the same.
The execution needs to move on to the next iteration of the loop when a valid option is processed. Hence, each valid option match ends with continue.
Silent error reporting (enabled when OPTSPEC starts with :) allows us to distinguish between INVALID OPTION and MISSING ARGUMENT. The later error is specific to the common options defined in File_1, thus it needs to be trapped there.
For more in-depth information on getopts, see Bash Hackers Wiki: Getopts tutorial

'less' the file specified by the output of 'which'

command 'which' shows the link to a command.
command 'less' open the file.
How can I 'less' the file as the output of 'which'?
I don't want to use two commands like below to do it.
=>which script
/file/to/script/fiel
=>less /file/to/script/fiel
This is a use case for command substitution:
less -- "$(which commandname)"
That said, if your shell is bash, consider using type -P instead, which (unlike the external command which) is built into the shell:
less -- "$(type -P commandname)"
Note the quotes: These are important for reliable operation. Without them, the command may not work correctly if the filename contains characters inside IFS (by default, whitespace) or can be evaluated as a glob expression.
The double dashes are likewise there for correctness: Any argument after them is treated as positional (as per POSIX Utility Syntax Guidelines), so even if a filename starting with a dash were to be returned (however unlikely this may be), it ensures that less treats that as a filename rather than as the beginning of a sequence of options or flags.
You may also wish to consider honoring the user's pager selection via the environment variable $PAGER, and using type without -P to look for aliases, shell functions and builtins:
cmdsource() {
local sourcefile
if sourcefile="$(type -P -- "$1")"; then
"${PAGER:-less}" -- "$sourcefile"
else
echo "Unable to find source for $1" >&2
echo "...checking for a shell builtin:" >&2
type -- "$1"
fi
}
This defines a function you can run:
cmdsource commandname
You should be able to just pipe it over, try this:
which script | less

RHEL6 getopts doesn't seem to be working

I have a new RHEL6 machine and I'm trying to run a script to generate some output. The script uses getopts which I've never used in the past. This should have worked on other machines but is my first time trying it. Below is the beginning of the script. Is there anything wrong with the syntax? When I try to output the variables it displays nothing:
#! /bin/sh
while getopts "h:u:g:o:e:y:bf" c
do
case "$c" in
u) USER=$OPTARG;;
g) GROUP=$OPTARG;;
o) OUT=$OPTARG;;
b) BATCH=1;;
f) FORCE=1;;
h) FQDN=$OPTARG;;
e) ENTITYID=$OPTARG;;
y) YEARS=$OPTARG;;
\?) echo "keygen [-o output directory (default .)] [-u username to own keypair] [-g owning groupname] [-h hostname for cert] [-y years to issue cert] [-e entityID to embed in cert]"
exit 1;;
esac
done
echo $FQDN
The echo displays a blank line.
You can't use question mark with the bash getopts (you also can't use the colon). In the case of question mark, getopts sets the value of the argument ($c in your case) to a question mark when the end of options has been encountered. It also uses question mark and colon as the value for the argument name when there's an error (specifically, ? is used when an invalid option is encountered or when in non-silent mode and a required option is not provided; colon is used in silent mode when a required option is not provided). In those error cases, OPTARG contains the offending argument. This is how POSIX getopts works as well.
The KSH getopts behaves differently, but it also excludes ? : (as well as - [ ] and only allowing # as the first option). It does, however, show a usage message when you provide -?. Basically, don't use -? with shell getopts. :)
Typically, I write a small function called "usage" and call it from both *) and by checking $? immediately after the case statement for non-zero value.

getopt command partially parsing

I have a part of bash script which suppose to do validate the arguments, if it matches then proceed or else exit.
Here is my script
TEMP=`getopt --options b,t:,h,n,v,z: --longoptions batch,targetdir:,help,notar,verbose,zone: --name 'mysql-backup-start' -- "$#"`
if [ $? -ne 0 ]; then
echo "Command Incoorect"
exit 1
fi
mysql-backup-start should take the following arguments: -b, -t, -h, -n, -v, -z --targetdir, --help, --notar, --verbose, and --zone. However, if i pass arguments like -nn, -hh, or --tar it works and it's not supposed to work.
To be more precise what i want, if i execute 'mysql-backup-start' should work, 'mysql- backup-start --notar' should work, 'mysql-backup-start --n' should not work, 'mysql-backup-start --targetdir=/home/backup/mysql' should work, 'mysql-backup-start --targetsdir=/home/backup/mysql' should not work, '--mysql-backup-start --ta=/home/backup/mysql' should not work.
When you pass -nn, getopt just interprets it as if you'd specified -n -n. This is often a valid way to pass arguments - For example, ssh -vvv runs with a much higher verbosity level than ssh -v. In other commands you have options to enable or disable features, and the last option (enable or disable) "wins." This is useful for example if the user has defined an alias like alias grep='grep -H' ("Print the file name for each match."), but wants to override it. To do that she could either run command grep or simply revert the option by running grep -h which is then resolved to grep -H -h.
If you want to check that an option has not been specified more than once (although this is usually not necessary), you should do that later when parsing $TEMP.
--tar should not work - See #tuxuday's comment and man getopt.

Resources