How to process more than 2 arguments in Bash? [duplicate] - bash

This question already has answers here:
An example of how to use getopts in bash
(8 answers)
Closed 3 years ago.
So, I want to pass 2 arguments and want those arguments to be processed in combination and separately too. I have coded the switch case to process them separately but I am not sure how to do process them together.
$ sh match_the_pattern.sh -a 6 -b 5 words.txt
Here,
-a: at least number of letters specified.
-b: at most number of letters specified.
What I am looking for?
when I get -a and -b together, first the script should list all the words which have at least given number of words, save, then out of those, it should process with the -b, that is the most number of letters in the result we got from the -a, then print the count.
This is a double filter, it is not like you posted both of them individually.
while [[ $# -gt 0 ]]; do
case $1 in
-a)
argument=$
egrep "^[[:alnum:]]{$argument,}$" $dictionyname | wc -l
shift ;;
-b)
arg=$2
egrep "^[[:alnum:]]{0,$argument}$" $dictionyname | wc -l
shift ;;
esac
shift
done
$ sh match_the_pattern.sh -a 6 -b 5 words.txt
7000
$ sh match_the_pattern.sh -a 6 words.txt
115690
$ sh match_the_pattern.sh -b 5 words.txt
12083
Note, I do know how to process -a and -b, I do not know how to combine the results of -a and -b when passed together in the argument...
If right now, I pass the command, I am getting this output :
$ sh match_the_pattern.sh -a 6 -b 5 words.txt
115690
12083
So it is processing a and b but giving the results one after another.

Set variables based on the parameters, then use them after the loop.
min=
max=
while [[ $# -gt 0 ]]; do
case $1 in
-a)
min=$2
shift ;;
-b)
max=$2
shift ;;
-*)
echo "Unknown option $1"
exit 1 ;;
*)
break ;;
esac
shift
done
if [[ -z $min && -z $max ]]
then
echo "At least one of -a and -b must be used"
exit 1
fi
egrep "^[[:alnum:]]{${min:-1},$max}$" "$#" | wc -l
${min:-1} means to use the value of $min, but if it's empty use 1 instead. So if they don't give -a, it will be {1,$max}.

Related

How to get everything after two specific characters in bash

I want to put the numbers into different variables
So I have three lines for example with $1 being -a in the first line
number -a 3 1245 1234
second line, $1= -b
number -b 2 2345 64353
third line, $1= -a
number -a 5 -b 3 54525 545252
Is it possible to put the numbers in one variable if a and b are separated like line one and two and in two variables if they are combined
So for line one
number=3
line 2
number=2
line 3
number1=5
number2=3
and then to use in a echo statement
Just do normal shell parsing for command line parameters. Not practical to create conditions for parsing for 3rd pattern "number=". Best to keep it to one for each of the options involved, that way you keep the distinct relationship with the original option flags.
#!/bin/sh
echo "number -a 3 1245 1234
number -b 2 2345 64353
number -a 5 -b 3 54525 545252" |
while read line
do
set -- ${line}
shift # eliminate command from line
while [ $# -gt 1 ]
do
case $1 in
-a ) echo "number1=$2" ; shift ; shift ;;
-b ) echo "number2=$2" ; shift ; shift ;;
* ) break ;;
esac
done
echo ""
done

Unable to workaround on getopt options [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I am trying to play around with getopt options. I want both short options and long options.
For my following test script, it does not produce my required output.
#!/bin/bash
options=$(getopt -o d:f:t: -l domain -l from -l to -- "$#")
[ $? -eq 0 ] || {
echo "Incorrect options provided"
exit 1
}
eval set -- "$options"
while true; do
case "$1" in
-d | --domain)
DOMAIN=$2;
shift 2
;;
-f | --from)
FROM=$2;
shift 2
;;
-t | --to)
TO=$2;
shift 2
;;
--)
shift
break
;;
esac
shift
done
echo "Domain is $DOMAIN"
echo "From address is $FROM"
echo "To address is $TO"
exit 0;
When I try to run it, nothing happens, it just hangs:
# bash getopt_check.sh -d hello.com -f from#test.com -t to#test.com
Expecting output:
Domain is hello.com
From address is from#test.com
To address is to#test.com
You are shifting 3 values per option, -d hello.com are 2 positions, not three.
-d | --domain)
...
shift 2
...
-f | --from)
...
shift 2
...
-t | --to)
...
shift 2
...
shift # shift 2 + shift = shift 3!
change it to:
-d|--domain)
shift
...
-f|--from)
shift
...
-t|--to)
shift
...
shift
Prefer to use lower case variables in your script - use upper case for exported variables. Check your script with http://shellcheck.net . Prefer not to use $?, instead check the command in if like if ! options=$(getopt ...); then echo "Incorrect....
while true; do is unsafe, in case you do not handle some option it will endlessly loop. Do while (($#)); do and handle that *) echo "Internal error - I forgot to add case for my option" >&2; exit 1; ;; in the case statement.

The advantage of shift in shell script over reassign value straightforward

I don't understand the shift in following code:
#! /usr/local/bin/bash
# process command line options
interactive=
filename=
while [[ -n $1 ]]; do
case $1 in
-f | --file) shift #don't understand the shift #No.1
filename=$1 ;;
-i | --interactive) interactive=1
;;
-h | --help) usage
exit;;
*) echo "usage >&2 exit 1";;
esac
shift # don't understand the shift #2
done
#interactive mode
if [[ -n $interactive ]]; then
echo "do something"
fi
#output html page
if [[ -n $filename ]]; then
if touch $filename && [[ -f $filename ]]; then
echo "write_html_page > $filename" #debug
else
echo "$program: Cannot write file $filename " >&2
exit 1
fi
else
echo "write_html_page to terminal" # debug
fi
Test it
$ bash question.sh -f test
write_html_page > test
$ bash question.sh -f
write_html_page to terminal
When I delete shift and change filename=$1 to filename=$2
$ bash question.sh -f
write_html_page to terminal
# it works properly
$ bash question.sh -f test
usage >&2 exit 1
write_html_page > test
# it almost function nicely except that `usage >&2 exit 1` is executed.
So the shift cannot be replaced by filename=$2 entirely.
The second shift at the botton if deleted, the loop run endlessly.
Could I interpret shift intuitively?
I did't find such a magic command in other languages.
shift will remove the first positional argument, and shift every other argument left one.
For example, let's consider the following:
#!/bin/bash
echo "$#"
shift
echo "$#"
shift
echo "$#"
Given that echo "$#" will print all of the arguments, if you were to run this, then the following would happen:
./test.bash 1 2 3 4 5
echo "$#" # prints 1 2 3 4 5
shift # Removes 1 and shifts everything else along
echo "$#" # prints 2 3 4 5
shift # shifting again
echo "$#" # prints 3 4 5
In your example, the script is parsing all of the flags. -i and -h are just switches and handle no following arguments. However, -f requires a filename.
The second shift will process the flag, shift the arguments, and then process them again. Therefore you can have ./program.bash -i -f filename. The -i will be shifted by the second shift, and then the filename will be processed on the next loop.
If you were to run ./program.bash -f filename -i, then the filename would need to be shifted along with the -f. Therefore, on the case block for -f there is an extra shift. In this example, -f would be shifted inside the case block, and then filename would be shifted by the second shift. Then the loop would run again to process any further flags.
As the while loop is [[ -n $1 ]], the loop will run until there are no more arguments.
The example on this page:
http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_07.html
explains what it's doing.
EDIT:
The example:
A shift statement is typically used when the number of arguments to a command is not known in advance, for instance when users can give as many arguments as they like. In such cases, the arguments are usually processed in a while loop with a test condition of (( $# )). This condition is true as long as the number of arguments is greater than zero. The $1 variable and the shift statement process each argument. The number of arguments is reduced each time shift is executed and eventually becomes zero, upon which the while loop exits.

bash - getopts only parses the first argument if operands are required

Once a bash program is executed while processing options in getops, the loop exits.
As a short example, I have the following bash script:
#!/usr/bin/env bash
while getopts ":a:l:" opt; do
case ${opt} in
a)
ls -a $2
;;
l)
ls -l $2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument" >&2
exit 1
;;
esac
done
echo -e "\nTerminated"
If the script is called test.sh, when I execute the script with this command, I get the following output, where only the -a flag is processed, and -l is ignored:
$ ./test.sh -al .
. .. file1.txt file2.txt test.sh
Terminated
However, if I remove the colons after each argument, indicating that operands are not required for each argument, then the script does as intended. If the while loop is changed to:
while getopts ":al" opt; do
Then, running my script gives the following output (with both -a and -l processed):
$ ./test.sh -al .
. .. file1.txt file2.txt test.sh
total 161
-rwxrwxrwx 1 root root 0 Nov 24 22:31 file1.txt
-rwxrwxrwx 1 root root 0 Nov 24 22:32 file2.txt
-rwxrwxrwx 1 root root 318 Nov 24 22:36 test.sh
Terminated
Additionally, adding something like OPTIND=1 to the end of my loop only causes an infinite loop of the script executing the first argument.
How can I get getopts to parse multiple arguments with option arguments (: after each argument)?
Speaking about short options only, there is no need for a space between an option and its argument, so -o something equals to -osomething. Although it's very common to separate them, there are some exceptions like: cut -d: -f1.
Just like #AlexP said, if you use while getopts ":a:l:" opt, then options -a and -l are expected to have an argument. When you pass -al to your script and you make the option -a to require an argument, getopts looks for it and basically sees this: -a l which is why it ignores the -l option, because -a "ate it".
Your code is a bit messy and as #cdarke suggested, it doesn't use the means provided by getopts, such as $OPTARG. You might want to check this getopts tutorial.
If I understand correctly, your main goal is to check that a file/folder has been passed to the script for ls. You will achieve this not by making the options require an argument, but by checking whether there is a file/folder after all the options. You can do that using this:
#!/usr/bin/env bash
while getopts ":al" opt; do
case ${opt} in
a) a=1 ;;
l) l=1 ;;
\?) echo "Invalid option: -$OPTARG" >&2; exit 1 ;;
:) echo "Option -$OPTARG requires an argument" >&2; exit 1 ;;
esac
done
shift $(( OPTIND - 1 ));
[[ "$#" == 0 ]] && { echo "No input" >&2; exit 2; }
input=("$#")
[[ "$a" == 1 ]] && ls -a "${input[#]}"
[[ "$l" == 1 ]] && ls -l "${input[#]}"
echo Done
This solution saves your choices triggered by options to variables (you can use an array instead) and later on decide based on those variables. Saving to variables/array gives you more flexibility as you can use them anywhere within the script.
After all the options are processed, shift $(( OPTIND - 1 )); discards all options and associated arguments and leaves only arguments that do not belong to any options = your files/folders. If there aren't any files/folders, you detect that with [[ "$#" == 0 ]] and exit. If there are, you save them to an array input=("$#") and use this array later when deciding upon your variables:
[[ "$a" == 1 ]] && ls -a "${input[#]}"
[[ "$l" == 1 ]] && ls -l "${input[#]}"
Also, unlike ls -a $2, using an array ls -a "${input[#]}" gives you the possibility to pass more than just one file/folder: ./test.sh -la . "$HOME".

How to combine getopts and positional parameters in bash? [duplicate]

This question already has answers here:
Is mixing getopts with positional parameters possible?
(9 answers)
Closed 5 years ago.
I want to use both getopts and positional parameters, but if I pass in a positional parameter to the program the getopts get lost.
directory=$1
while getopts l: flag; do
case "$flag" in
l) level=$OPTARG;;
esac
done
if [ -n "$level" ]; then
echo "Level exist!"
else
echo "Level doesn't exist!"
fi
So when I run the program like this:
sh myprogram.sh ~/documents -l 2
I expect:
Level exist!
And instead it returns:
Level doesn't exist!
The thing is, if I run the program without the positional parameter (~/documents) like this:
sh myprogram.sh -l 2
I get the correct output:
Level exist!
Why is that? How can I use both positional parameters and getopts in bash?
Thanks!
Most tools are written in the form: tool [options] arg ...
So you would do this:
# first, parse the options:
while getopts l: flag; do
case "$flag" in
l) level=$OPTARG;;
\?) exit 42;;
esac
done
# and shift them away
shift $((OPTIND - 1))
# validation
if [ -n "$level" ]; then
echo "Level exist!"
else
echo "Level doesn't exist!"
fi
# THEN, access the positional params
echo "there are $# positional params remaining"
for ((i=1; i<=$#; i++)); do
printf "%d\t%s\n" $i "${!i}"
done
Use the \? to abort the script if the user provides an unknown option or fails to provide a required argument
And invoke it like:
$ bash test.sh
Level doesn't exist!
there are 0 positional params remaining
$ bash test.sh -l 2
Level exist!
there are 0 positional params remaining
$ bash test.sh -l 2 foo bar
Level exist!
there are 2 positional params remaining
1 foo
2 bar
$ bash test.sh -x
test.sh: illegal option -- x
$ bash test.sh -l
test.sh: option requires an argument -- l
But you cannot put the options after the arguments: getopts stops when the first non-option argument is found
$ bash test.sh foo bar -l 2
Level doesn't exist!
there are 4 positional params remaining
1 foo
2 bar
3 -l
4 2

Resources