Bash script - how to read file line by line while using getopts - bash

I want to do two things in this script:
1) pass a file name to the script
2) pass options to the script
example 1:
$./test_script.sh file_name_to_be_read
pass only file names to script
example 2:
$./test_script.sh -a -b file_name_to_be_read
pass file name and options to script
I am able to get example 1 to work using the following codes:
while read -r line ; do
echo $line
done
In example 2, I want to add additional flags like these:
while getopts "abc opt; do
case "$opt" in
a) a=1
echo "a is enabled"
;;
b) b=1
echo "b is enabled"
;;
esac
done
but how do I make it so that the file_name to be mandatory and be used with or without options?

getopts only parses options (arguments starting with -); the other arguments are left alone. The parameter OPTIND tells you the index of the first argument not yet looked at; typically you discard the options before this.
while getopts "ab" opt; do
case "$opt" in
a) a=1
echo "a is enabled"
;;
b) b=1
echo "b is enabled"
;;
esac
done
shift $((OPTIND - 1))
echo "$# arguments remaining"
for arg in "$#"; do
echo "$arg"
done
The preceding, if called as bash tmp.bash -a -b c d e, produces
$ bash tmp.bash -a -b c d e
a is enabled
b is enabled
3 arguments remaining:
c
d
e

Related

Extending getopts from sourced script

I am trying to extend the getopts from the sourced script as below.
if an option other than a,b,c,d is passed in, it needs to print "invalid option" and exit.
script_a.sh:
#!/bin/bash
script_a_getopts() {
while getopts 'a:b:' OPT; do
case "$OPT" in
a)
a=$OPTARG
;;
b)
b=$OPTARG
;;
*)
echo "invalid option"
exit 1
;;
esac
done
}
script_b.sh
#!/bin/bash
source script_a.sh
while getopts ':c:d:' OPT; do
case "$OPT" in
c)
c=$OPTARG
;;
d)
d=$OPTARG
;;
[?])
script_a_getopts $#
esac
done
echo "a=$a"
echo "b=$b"
echo "c=$c"
echo "d=$d"
When I run the script, it don't work as expected, obviously I am making a mistake.
$ ./script_b.sh -c cat -d dog -a apple -b boy
a=
b=
c=cat
d=dog
Didn't throw error when -x is passed.
$ ./script_b.sh -x
a=
b=
c=
d=
Short Answer: You have to rollback the OPTIND before calling script_a_getopts.
case "$OPT" in
c)
c=$OPTARG
;;
...
[?])
let OPTIND--
script_a_getopts $#
esac
done
Long Answer:
The getopts track which arguments have been processed using the OPTIND variable. When the top getopts recognized unknown item, it has already 'consumed' it by moving OPTIND to the next argument. To get script_a_getopts to process that argument, the OPTIND need to be rolled back to point to the unprocessed argument.
The let OPTIND-- will allow the unrecognized argument to be re-processed.
Side note, if you want to allow options to be placed in arbitrary order (-c cat -a apple -d dog -b boy) you will have to repeat the same in the script_a_getopts. This will require improved error processing in script_a_getopts function.

Shell script not reading getopts arguments on MacOS

I have written a script that performs tasks according to switches used on command line. Here is my script:
Expects the git branch name and two optional switches
-r for resetting database
-t for merging training data
test.sh
merge_training=false
reset_database=false
while getopts ":tr" opt; do
case ${opt} in
t ) # process option t
echo "one"
$merge_training=true
;;
r ) # process option r
echo "two"
$reset_database=true
;;
esac
done
echo $reset_database
echo $merge_training
When I run this script with command:
sh test.sh branchname -r -t
It does not print one or two and the last statements prints:
false
false
What is wrong here?
What is wrong is your assignments. Your should have got 2 error messages saying:
false=true: command not found
Hint: Never put a $ on the left-side of an assignment:
The convention is to put options first, then extra parameters, so your command-line should be:
bash test.sh -r -t branchname
Use bash not sh. sh is a POSIX shell and is roughly a subset of bash (its complicated). Don't confuse the two.
merge_training=false
reset_database=false
while getopts ":tr" opt; do
case ${opt} in
t ) # process option t
echo "one"
merge_training=true # <<<<<<<<<<<<<<<
;;
r ) # process option r
echo "two"
reset_database=true # <<<<<<<<<<<<<<<
;;
esac
done
shift $(( OPTIND-1 ))
extra="$1"
echo $reset_database
echo $merge_training
echo $extra
when you remove the branchname as a param, then it works (- but you have to also remove the $ from $merge_training=true, else you try to false=true..)
So what you could do is to save the $1 param in a variable and simply shift.
Here the code:
#!/bin/bash
merge_training=false
reset_database=false
branchname="$1"
shift
while getopts ":tr" opt; do
case $opt in
t ) # process option t
echo "one"
merge_training=true
;;
r ) # process option r
echo "two"
reset_database=true
;;
esac
done
echo $branchname
echo $reset_database
echo $merge_training
Some of the points you could enhance on your script,
Variable assignments don't take $ on the left-hand-side
The command-line arguments are seeing an extra argument branchname before processing the actual OPTSTRING, you need to exclude the first parameter before getting into the getopts() call.
Always set the shell interpreter to the one you want to run your script with. In bash do #!/usr/bin/env bash
Always quote your shell variables unless you have a good reason not to.
The updated script should look like below if you are passing arguments as bash test.sh branchname -r -t
#!/usr/bin/env bash
merge_training=false
reset_database=false
branchname="$1"
shift 1
while getopts ":tr" opt; do
case "${opt}" in
t ) # process option t
echo "one"
merge_training=true
;;
r ) # process option r
echo "two"
reset_database=true
;;
esac
done
echo "$reset_database"
echo "$merge_training"

Bash Shell - Reading option with multiple letters

I am working on a shell script that allows users to set options.
The script is working fine if the user specifies options seperately. For instance:
./my_file.sh -r -l directory1 directory2
The command above is working perfectly, however my script doesn't work if the user specifies the following command:
EDIT: What I mean is that the -l option is not recognized if the user enters the command below
./my_file.sh -rl directory1 directory2
This is the code I am using to read the options
while getopts 'lvibrd:v' flag; do
case "${flag}" in
l) option_l=true
shift ;;
v) option_v=true
shift ;;
i) option_i=true
shift ;;
b) option_b=true
shift ;;
r) option_r=true
shift ;;
d) option_d=true
shift ;;
*) echo "Invalid options ${flag}"
exit 1 ;;
esac
done
Is there a way to read options with multiple letters, such as -rl, using similar code?
Thank you in advance.
Try the following (simplified):
while getopts 'lr' flag; do
case "$flag" in
l) echo "option -l was set";;
r) echo "option -r was set";;
*) echo "Invalid options ${flag}"
exit 1 ;;
esac
done
shift $(($OPTIND - 1))
echo "$#"
Omitted shift makes the difference. The shifting confuses getopts builtin.
This works correctly for both single and combined options:
$ ./nic.sh -rl hello world
option -r was set
option -l was set
hello world
EDIT: I've added code to print the rest of arguments (after those processed by getopts).

Pass parameters as option in custom getopts script in bash

I'd like to pass options as a parameter. E.g.:
mycommand -a 1 -t '-q -w 111'
The script cannot recognize a string in quotes. I.e it gets only part of the string.
getopts works the same - it see only -q.
For custom getopts I use similar script (example):
while :
do
case $1 in
-h | --help | -\?)
# Show some help
;;
-p | --project)
PROJECT="$2"
shift 2
;;
-*)
printf >&2 'WARN: Unknown option (ignored): %s\n' "$1"
shift
;;
*) # no more options. Stop while loop
break
;;
--) # End of all options
echo "End of all options"
shift
break
;;
esac
done
Maybe I misunderstand the question, but getopts seems to work for me:
while getopts a:t: arg
do
case $arg in
a) echo "option a, argument <$OPTARG>"
;;
t) echo "option t, argument <$OPTARG>"
;;
esac
done
Run:
bash gash.sh -a 1 -t '-q -w 111'
option a, argument <1>
option t, argument <-q -w 111>
Isn't that what you want? Maybe you missed the : after the options with arguments?

bash: getopts: parse two options with arguments

I am trying to parse two options which both need an argument.
#!/bin/bash
while getopts "a:b:" opt; do
case $opt in
a)
echo "a has the argument $OPTARG."
shift
;;
b)
echo "b has the argument $OPTARG."
shift
;;
esac
done
What I expect is that this script prints out the arguments of a and b. But the output is only:
$ sh ./cmd_parse_test.sh -a foo -b bar
a has the argument foo.
What am I doing wrong?
You don't have to shift to get the next argument. Simply dump whatever you want, and continue for the next iteration, as in:
#!/bin/bash
while getopts "a:b:" opt; do
case $opt in
a)
echo "a has the argument $OPTARG."
;;
b)
echo "b has the argument $OPTARG."
;;
esac
done
Which outputs:
$ ./cmd_parse_test.sh -a foo -b bar
a has the argument foo.
b has the argument bar.
Notice also that you don't have to run the script with sh, since you've already set the shbang to use bash.

Resources