Bash getopts not reading arguments to variables - bash

Hi all I think I am going insane. I have the following at the beginning of a bash script:
#!/bin/bash
while getopts i:o:c:p:g: flag
do
case "${flag}" in
g) genome=${OPTARG};;
p) poscol=${OPTARG};;
c) chromcol=${OPTARG};;
i) inf=${OPTARG};;
o) outf=${OPTARG};;
esac
done
echo ""
echo "This should be a list of the arguments:"
echo $inf
echo $outf
echo $poscol
echo $chromcol
echo $genome
echo "-----------------------"
Yet when I run a test of the code, eg:
bash Code.sh -i test.txt -o test.txt -c 0 -p 1 -g testgenome
bash returns:
This should be a list of the arguments:
-----------------------
I literally just copied this section of code from a script where the arguments work fine. Does anyone have any idea what could be going on here? Thanks in advance.

Related

How can I get filename and argument with getopts from Command

Here is the code, I want to run this command
Getting the test.log input, then convert it to text type, then store it on the output.txt file
./small.sh test.log -t text -o output.txt
#!/usr/bin/env bash
usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; }
[ $# -eq 0 ] && usage
parse()
{
local file=$1
local type=$2
local output=$3
echo "file: ${file}, type: ${type}, output: ${output}"
}
while getopts ":ht:o:" arg; do
case $arg in
t) # specify type.
type=${OPTARG} ;;
o) # specify directory.
output=${OPTARG} ;;
h | *) # Display help.
usage
exit 0
;;
esac
done
shift $(( OPTIND - 1))
parse "${file}" "${type}" "${output}"
with this code i can only get filename when i put it in unsort order like this
./small.sh -t text -o output.txt test.log
How can I get filename and argument with getopts
./small.sh test.log -t text -o output.txt
It is not possible. getopts only supports positional arguments after option arguments.
You can:
accept the limitation
use something elsem, like GNU getopt.
write your own option parsing code

Why do shortened versions of long options work with getopt?

In the following script:
#!/usr/bin/env bash
func_usage ()
{
cat <<EOF \
USAGE: ${0} \
EOF
}
## Defining_Version
version=1.0
## Defining_Input
options=$(getopt -o "t:" -l "h,help,v,version,taxonomy:" -a -- "$#")
eval set -- "$options"
while true;do
case $1 in
-h|--h|-help|--help)
func_usage
exit 0
;;
-v|--v|-version|--version)
echo $version
;;
-t|--t|-taxonomy|--taxonomy)
echo "Option t = $2 ";
Taxonomy_ID=$2
echo $Taxonomy_ID
shift
;;
--)
shift
break;;
esac
shift
done
## Defining Taxonomy Default Value (in case is not provided)
TaxonomyID=${Taxonomy_ID:=9606};
echo $TaxonomyID
exit 0
The commands:
./script.sh -v
./script.sh --v
./script.sh -version
./script.sh --version
Work as expected. But what I do not understand is why the commands:
./script.sh -ver
./script.sh --ver
work at all. An equivalent unexpected behavior is also observed for the commands:
./script.sh -tax 22
./script.sh --tax 22
I would be grateful to get an explanation and/or a way to correct this unexpected behavior.
Note that getopt is an external utility unrelated to Bash.
what I do not understand is why the commands: .. work at all.
Because getopt was designed to support it, there is no other explanation. From man getopt:
[...] Long options may be abbreviated, as long as the abbreviation is not ambiguous.
Unambiguous abbreviations of long options are converted to long options.
Based on the comments I have received, specially from #CharlesDuffy, I have modified my code to what I believe is a more robust and compatible version. Importantly, the code below addresses the pitfalls of the original code
#!/usr/bin/env bash
func_usage ()
{
cat <<EOF
USAGE: ${0}
EOF
## Defining_Version
version=1.0
## Defining_Input
while true;do
case $1 in
-h|--h|-help|--help|-\?|--\?)
func_usage
exit 0
;;
-v|--v|-version|--version)
echo $version
;;
-t|--t|-taxonomy|--taxonomy)
echo "Option t = $2 ";
Taxonomy_ID=$2
echo $Taxonomy_ID
shift
;;
--)
shift
break;;
-?*)
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*)
break
esac
shift
done
TaxonomyID=${Taxonomy_ID:=9606};
echo $TaxonomyID
exit 0
The code above behaves as expected in that the commands:
./script -tax 22
Gives the warning:
WARN: Unknown option (ignored): -tax
9606
As expected

Handling unused getopts argument

I have a script that starts with getopts and looks as follows:
USAGE() { echo -e "Usage: bash $0 [-w <in-dir>] [-o <out-dir>] [-c <template1>] [-t <template2>] \n" 1>&2; exit 1; }
if (($# == 0))
then
USAGE
fi
while getopts ":w:o:c:t:h" opt
do
case $opt in
w ) BIGWIGS=$OPTARG
;;
o ) OUTDIR=$OPTARG
;;
c ) CONTAINER=$OPTARG
;;
t ) TRACK=$OPTARG
;;
h ) USAGE
;;
\? ) echo "Invalid option: -$OPTARG exiting" >&2
exit
;;
: ) echo "Option -$OPTARG requires an argument" >&2
exit
;;
esac
done
more commands etc
echo $OUTDIR
echo $CONTAINER
I am fairly new to getopts. I was doing some testing on this script and at some stage, I didn't need/want to use the -c argument [-c ]. In other words, I was trying to test another specific part of the script not involving the $CONTAINER variable at all. Therefore, I simply added # in front of all commands with the $CONTAINER and did some testing which was fine.
When testing the script without using $CONTAINER, I typed:
bash script.bash -w mydir -o myoutdir -t mywantedtemplate
However, I was wondering, given my getopts command I didn't get a warning. In other words, why did I not get a warning asking for -c argument. Is this possible? Does the warning only occur if I type:
bash script.bash -w mydir -o myoutdir -t mywantedtemplate -c
UPDATE
After doing some testing, I think that is it:
If you don't explicitly write "-c", getopts won't "ask" you for it and give you an error (unless your script is doing something with it - i.e. if you haven't put # in front of each command using this argument)
You only get an error if you put "-c "
Is this correct?
getopts does not warn when some options are not used (i.e. they are optional). Usually that's a good thing because some options (e.g. -h) are not used with other options. There is no way to specify mandatory options directly with the Bash builtin getopts. If you want mandatory options then you will need to write code to check that they have been used. See bash getopts with multiple and mandatory options. Also (as you have found), you won't get an error if you fail to write code to handle options specified in the optstring (first) argument to getopts.
You could get a kind of automatic warning for mandatory arguments by using the nounset setting in your Bash code (with set -o nounset or set -u). That would cause warnings to be issued for code like echo $CONTAINER if the -c option is not specified so $CONTAINER is not set. However, using the nounset option would mean that all of your code needs to be written more carefully. See How can I make bash treat undefined variables as errors?, including the comments and "Linked" answers, for more information.
You might use this script:
printf captures the stdout and put into the stderr and then back to the stdout also captures the exit code, so we can handle if the code is greater than 0.
some_command() {
echo 'this is the stdout'
echo 'this is the stderr' >&2
exit 1
}
run_command() {
{
IFS=$'\n' read -r -d '' stderr;
IFS=$'\n' read -r -d '' stdout;
(IFS=$'\n' read -r -d '' _ERRNO_; exit ${_ERRNO_});
} < <((printf '\0%s\0%d\0' "$(some_command)" "${?}" 1>&2) 2>&1)
}
echo 'Run command:'
if ! run_command; then
## Show the values
typeset -p stdout stderr
else
typeset -p stdout stderr
fi
Just replace some_command with getopts ":w:o:c:t:h"

Allow only one letter in a switch bash

I want my bash script to only accept one letter in the switch, with nothing after it.
Here's my code (foo.sh):
#!/bin/bash
while getopts ":ap" jeep;do
case $jeep in
a)
echo 'Hi Brad'
;;
b)
echo 'Hi Eddy'
;;
else)
echo 'Hi whoever'
\?)
echo 'Bad input' >&2
exit 1
;;
esac
done
I want it to act like this:
$ foo.sh -a
Hi Brad
$ foo.sh -b
Hi Eddy
$ foo.sh
Hi whoever
$ foo.sh -r
Bad input
$ foo.sh -ab
Bad input
$ foo.sh -ba
Bad input
Instead, it runs both a and b (shown below) if I use -ab, which I don't want it to.
$ foo.sh -ab
Hi Brad
Hi Eddy
Also, else) doesn't work like I'd like it to. I want it to say Hi whoever if no switches are typed.
How can I fix this?
getopt does not have a way of specifying mutually exclusive flags, so you have to create your own. The simplest way to do this is probably something like this:
while getopts ":ab" jeep
do
case $jeep in
a)
if [ -n "${run-}" ]
then
exit 1
fi
echo 'Hi Brad'
run=1
;;
b)
if [ -n "${run-}" ]
then
exit 1
fi
echo 'Hi Eddy'
run=1
;;
\?)
if [ -n "${run-}" ]
then
exit 1
fi
echo 'Hi whoever'
run=1
;;
esac
done

Check if file exists [BASH]

How do I check if file exists in bash?
When I try to do it like this:
FILE1="${#:$OPTIND:1}"
if [ ! -e "$FILE1" ]
then
echo "requested file doesn't exist" >&2
exit 1
elif
<more code follows>
I always get following output:
requested file doesn't exist
The program is used like this:
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
Any ideas please?
I will be glad for any help.
P.S. I wish I could show the entire file without the risk of being fired from school for having a duplicate. If there is a private method of communication I will happily oblige.
My mistake. Fas forcing a binary file into a wrong place. Thanks for everyone's help.
Little trick to debugging problems like this. Add these lines to the top of your script:
export PS4="\$LINENO: "
set -xv
The set -xv will print out each line before it is executed, and then the line once the shell interpolates variables, etc. The $PS4 is the prompt used by set -xv. This will print the line number of the shell script as it executes. You'll be able to follow what is going on and where you may have problems.
Here's an example of a test script:
#! /bin/bash
export PS4="\$LINENO: "
set -xv
FILE1="${#:$OPTIND:1}" # Line 6
if [ ! -e "$FILE1" ] # Line 7
then
echo "requested file doesn't exist" >&2
exit 1
else
echo "Found File $FILE1" # Line 12
fi
And here's what I get when I run it:
$ ./test.sh .profile
FILE1="${#:$OPTIND:1}"
6: FILE1=.profile
if [ ! -e "$FILE1" ]
then
echo "requested file doesn't exist" >&2
exit 1
else
echo "Found File $FILE1"
fi
7: [ ! -e .profile ]
12: echo 'Found File .profile'
Found File .profile
Here, I can see that I set $FILE1 to .profile, and that my script understood that ${#:$OPTIND:1}. The best thing about this is that it works on all shells down to the original Bourne shell. That means if you aren't running Bash as you think you might be, you'll see where your script is failing, and maybe fix the issue.
I suspect you might not be running your script in Bash. Did you put #! /bin/bash on the top?
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
You may want to use getopts to parse your parameters:
#! /bin/bash
USAGE=" Usage:
script.sh [-g] [-p] [-r FUNCTION_ID|-d FUNCTION_ID] FILE
"
while getopts gpr:d: option
do
case $option in
g) g_opt=1;;
p) p_opt=1;;
r) rfunction_id="$OPTARG";;
d) dfunction_id="$OPTARG";;
[?])
echo "Invalid Usage" 1>&2
echo "$USAGE" 1>&2
exit 2
;;
esac
done
if [[ -n $rfunction_id && -n $dfunction_id ]]
then
echo "Invalid Usage: You can't specify both -r and -d" 1>&2
echo "$USAGE" >2&
exit 2
fi
shift $(($OPTIND - 1))
[[ -n $g_opt ]] && echo "-g was set"
[[ -n $p_opt ]] && echo "-p was set"
[[ -n $rfunction_id ]] && echo "-r was set to $rfunction_id"
[[ -n $dfunction_id ]] && echo "-d was set to $dfunction_id"
[[ -n $1 ]] && echo "File is $1"
To (recap) and add to #DavidW.'s excellent answer:
Check the shebang line (first line) of your script to ensure that it's executed by bash: is it #!/bin/bash or #!/usr/bin/env bash?
Inspect your script file for hidden control characters (such as \r) that can result in unexpected behavior; run cat -v scriptFile | fgrep ^ - it should produce NO output; if the file does contain \r chars., they would show as ^M.
To remove the \r instances (more accurately, to convert Windows-style \r\n newline sequences to Unix \n-only sequences), you can use dos2unix file to convert in place; if you don't have this utility, you can use sed 's/'$'\r''$//' file > outfile (CAVEAT: use a DIFFERENT output file, otherwise you'll destroy your input file); to remove all \r instances (even if not followed by \n), use tr -d '\r' < file > outfile (CAVEAT: use a DIFFERENT output file, otherwise you'll destroy your input file).
In addition to #DavidW.'s great debugging technique, you can add the following to visually inspect all arguments passed to your script:
i=0; for a; do echo "\$$((i+=1))=[$a]"; done
(The purpose of enclosing the value in [...] (for example), is to see the exact boundaries of the values.)
This will yield something like:
$1=[-g]
$2=[input.txt]
...
Note, though, that nothing at all is printed if no arguments were passed.
Try to print FILE1 to see if it has the value you want, if it is not the problem, here is a simple script (site below):
#!/bin/bash
file="${#:$OPTIND:1}"
if [ -f "$file" ]
then
echo "$file found."
else
echo "$file not found."
fi
http://www.cyberciti.biz/faq/unix-linux-test-existence-of-file-in-bash/
Instead of plucking an item out of "$#" in a tricky way, why don't you shift off the args you've processed with getopts:
while getopts ...
done
shift $(( OPTIND - 1 ))
FILE1=$1

Resources