How do I collect arguments from the command-line? [duplicate] - linux

This question already has answers here:
How do I parse command line arguments in Bash?
(40 answers)
Closed 3 years ago.
I am very new to shell scripting and using CLI all together. However, I want to create a simple program that can collect arguments from a user.
Optimally, I want my shell script to get a network name and instance name from the user, and assign the values to variables.
I know that $# can be used to gather arguments, but are there any other ways? I often notice when I make a command, there is, for example, something like this: $create --instance_name NAME . Can I gather an argument by using -- to specify the parameter? If so, here is an example of the command:
$collect_variable.sh --network NETWORK_NAME --instance_name INSTANCE_ID
Once again, thank you for any help. I am very new to stack overflow and unix all together, and any help is appreciated.

You can using a variable to assign to for example:
while getopts "x:y:z:" res
do
case "$res" in
x ) paramX="$OPTARG" ;;
y ) paramY="$OPTARG" ;;
z ) paramX="$OPTARG" ;;
? ) echo "not a valid value"
esac
done
Check the rest of the links that other StackO users sent, they provide great examples!
Hope that can help you solve your issue.
Peace!

There are lots of ways of gathering arguments. You can name their position, e.g. $1 is the first argument and $14 is the fourteenth.
You can refer to the argument list like $# (this preserves spacing) or like $* (this collapses spacing):
test_args() {
echo '# as "$#"'
for argument in "$#"; do
echo "$argument"
done
echo '# as "$*"'
for argument in "$*"; do
echo "$argument"
done
echo '# as $*'
for argument in $*; do
echo "$argument"
done
}
$ test_args "1 2" three four
# as "$#"
1 2
three
four
# as "$*"
1 2 three four
# as $*
1
2
three
four
Since you're exclusively using long options separated from their arguments by spaces,
while [ "$#" -gt 0 ]; do
case "$1" in
( --network ) NETWORK="$2"; shift ;;
( --network=?* ) NETWORK="${1#*=}" ;;
( --instance_name ) INSTANCE_NAME="$2"; shift ;;
( --instance_name=?* ) INSTANCE_NAME="${1#*=}" ;;
( -* ) echo "Illegal option -- $1" >&2; exit 2 ;;
( * ) break ;; # perhaps non-options are used later
esac
shift
done
This loops on each option and parses it. There are two conditions for each option, which lets us handle when the arguments are spaced from the options (--network bobnet) or when they're assigned to the options (--network=bobnet). Spaced means we need to refer to the next argument (thus the extra shift) while assigned means we need to use parameter subsitution to remove the characters at the front of the string up until (and including) the first =.
The final shift pulls the first item off of the argument list so we can operate on the next one. Shifting separately for the two-argument clauses (rather than ending with shift 2) also allows for binary options like --verbose, which doesn't contain an argument.
You can also use a while getopts loop to support a mix of short and long arguments; see my more detailed post on getopts.

Related

how to call a function with 2 arguments which under the option of "getopts"

New in Linux bash script.
Here I tried to create some files with getopts. For example I'd like to create 3 files called xyzfile, in command line ./createfiles -n xyzfile 3should be given (2 arguments after the option -n). The result should be 3 files with the names xyzfile_1, xyzfile_2 and xyzfile_3.
I tried to put my createfile() function outside the while-loop and as well as inside the while-loop. But the option -n doesn't work.
I also tried to create another function called foo() with included the function createfile(), but still something wrong there.
I have no idea anymore what I can do. Hope I can get some advices from you guys. Thank you very much!
#!/bin/bash
while getopts :n:bc opt; do
case $opt in
n) echo test 3333333
createfile() {
echo "$OPTARG"
sum=$2
for((i=1;i<=sum;i++))
do
touch "$OPTARG_${i}"
done
}
createfile $OPTARG ${2};;
b) echo "test 1111111";;
c) echo "test 2222222";;
*) echo error!;;
esac
done
Use a separate option for the count, and create your files after the option processing.
Something like:
while getopts "n:c:" opt; do
case $opt in
n) name="$OPTARG";;
c) count=$OPTARG;;
# other options...
esac
done
shift $((OPTIND -1))
while (( count > 0 )); do
touch "${name}_$count"
(( count-- ))
# ...
done
getopts supports only options without, or with one argument. So you'll have to decide on which way you want your script to work. You have multiple options:
add a new option -m or similar to pass the maximum number of files you want to create: createfile -n xyzfile -m 3
you can also use the arguments that are not passed as an option, if you do your parsing well then createfile 3 -n xyzfile or createfile -n xyzfile 3 would mean the same. In my scripts I often use such positional argument if there is one option that the user always needs to pass.
You might even consider changing your way of calling the script to createfile xyzfile -n 3 or even createfile xyzfile where the name is a positional argument and the number of files optional (choose a logical default value, probably 1)...
Parse the options first, then use the values you discover. An option can take only a single argument, so -n only gets the first one (I'll keep that as the file-name stem here). The count will be an ordinary positional argument found after parsing the options.
while getopts :n:bc opt; do
case $opt in
n) stem=$OPTARG; shift 2;;
b) shift 1;;
c) shift 1;;
*) shift 1; echo error ;;
esac
done
count=${1?No count given}
createfile () {
for ((i=$1; i<=$2; i++)); do
touch "${1}_${i}"
done
}
createfile "$stem" "$count"

Parsing long and short args in ksh using loop

I am trying to parse arguments in ksh. Can't do getopt for the same as in short options I have two/three characters. Currently I am using for loop. Its stupid but am unable to find something better.
Question: How do I set option+value as one unit in order to parse?
Also if eval set -- $option will help me then how do I use it? echo on option does not show the expected "--" at the end. Am I assuming something wrong?
I am thinking of using a variable to keep track of when an option is found but this method seems too confusing and unnecessary.
Thanks for your time and help.
Update 1:
Adding code as pointed out. Thanks to markp, Andre Gelinas and random down-voter in making this question better. Trying to execute the script as given in line 2 and 3 of code - or any other combination of short and long options passed together.
#!/bin/ksh
# bash script1.sh --one 123 --two 234 --three "some string"
# bash script1.sh -o 123 -t 234 -th "some string"
# the following creates problems for short options.
#options=$(getopt -o o:t:th: -l one:two:three: "--" "$#")
#Since the below `eval set -- "$options"` did not append "--" at the end
#eval set -- "$options"
for i in $#; do
options="$options $i"
done
options="$options --"
# TODO capture args into variables
Attempted code below TODO until now:
for i in $options; do
echo $i
done
Will be capturing the args using:
while true; do
case $1 in
--one|-o) shift; ONE=$1
;;
--two|-t) shift; TWO=$1
;;
--three|-th) shift; THREE=$1
;;
--) shift; break
;;
esac
done
Try something like this :
#!/bin/ksh
#Default value
ONE=123
TWO=456
# getopts configuration
USAGE="[-author?Andre Gelinas <andre.gelinas#foo.bar>]"
USAGE+="[-copyright?2018]"
USAGE+="[+NAME?TestGetOpts.sh]"
USAGE+="[+DESCRIPTION?Try out for GetOps]"
USAGE+="[o:one]#[one:=$ONE?First.]"
USAGE+="[s:second]#[second:=$TWO?Second.]"
USAGE+="[t:three]:[three?Third.]"
USAGE+=$'[+SEE ALSO?\aman\a(1), \aGetOpts\a(1)]'
while getopts "$USAGE" optchar ; do
case $optchar in
o) ONE=$OPTARG ;;
s) TWO=$OPTARG ;;
t) THREE=$OPTARG ;;
esac
done
print "ONE = "$ONE
print "TWO = "$TWO
print "THREE = "$THREE
You can use either --one or -o. Using --man or --help are also working. Also -o and -s are numeric only, but -t will take anything. Hope this help.

Passing quoted as arguments to the function

I would like to find out answer on probably quite simple question: I would like to pass quoted strings with whitespaces inside as a standalone arguments for function.
There is the following file with data (for example):
one
two three
four five six
seven
And there is script with 2 simple functions:
params_checker()
{
local first_row="$1"
local second_row="$2"
local third_row="$3"
echo "Expected args are:${first_row} ; ${second_row} ; ${third_row}"
echo "All args are:"
for arg in "$#"; do
echo "${arg}"
done
}
read_from_file()
{
local args_string
while read line; do
args_string="${args_string} \"${line}\""
echo "Read row: ${line}"
done < ./test_input
params_checker ${args_string}
}
read_from_file
In other words I would like to get rows from text file as arguments to function params_checker (each row from file as different parameter, I need to keep whitespaces in the rows). Attempt to make combined string with quoted "substrings" was failed, and output was:
~/test_sh$ sh test_process.sh
Read row: one
Read row: two three
Read row: four five six
Read row: seven
Expected args are:"one" ; "two ; three"
All args are:
"one"
"two
three"
"four
five
six"
"seven"
Expectation is $1="one", $2="two three", $3="four five six" ...
Quoting of ${args_string} during passing to params_checker gave another result, string is passed as a single argument.
Could you please help to find out correct way how to pass such strings with whitespaces from file as a different standalone function argumets?
Thanks a lot for help!
In bash/ksh/zsh you'd use an array. In sh, you can use the parameters "$1", "$2" etc:
read_from_file()
{
set -- # Clear parameters
while read line; do
set -- "$#" "$line" # Append to the parameters
echo "Read row: ${line}"
done < ./test_input
params_checker "$#" # Pass all parameters
}
There you go, this should give you what you are looking for:
#!/bin/bash
params_checker()
{
local first_row="$1"
local second_row="$2"
local third_row="$3"
local forth_row="$4"
echo "Expected args are: ${first_row} ; ${second_row} ; ${third_row} ; ${forth_row}"
echo "All args are:"
for i in "$#"
do
echo "$i"
done
}
read_from_file()
{
ARRAY=()
while read line; do
echo "Read row: ${line}"
ARRAY+=("$line")
done < ./test_input
params_checker "${ARRAY[#]}"
}
read_from_file;
That should work fine in BASH. If your file is named test.sh, you can run it like this ./test.sh

Bash for loop on variable numbers

I have a situation where I have large number of numbered variables. I want to evaluate each variable and set variable to a specific string if the condition is matched.
#!/bin/bash
var1=""
var2="1233123213"
var3="22332323222324242"
var4=""
var5=""
for i in 1 2 3 4 5
do
if [ -z "$var{$}i" ]
then
var{$}i="None"
fi
echo "var{$}i \r"
done
but the problem is when I run the script I get following.
{1} \r
{2} \r
{3} \r
{4} \r
{5} \r
How I can fix this.
Use indirect variable expansion in bash with syntax {!var}.
From the man bash page,
If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion. The exclamation point must immediately follow the left brace in order to introduce indirection.
Modify your code to something like below,
for i in 1 2 3 4 5
do
var="var$i"
[ -z "${!var}" ] && declare "var$i"="none"
done
printf "var1=%s\n" "$var1"
printf "var2=%s\n" "$var2"
printf "var3=%s\n" "$var3"
printf "var4=%s\n" "$var4"
printf "var5=%s\n" "$var5"
The syntax "${!var}" in this case evaluates the value of the variable within the string var which is var1, var2, var3... and the declare syntax sets the variable value at run-time, only for those variables that are empty.
Now on printing those variables produces,
var1=none
var2=1233123213
var3=22332323222324242
var4=none
var5=none
Indirect assignment will work here, but in this specific case arrays seem like a good fit :
#!/bin/bash
declare -a var=()
var+=("")
var+=(1233123213)
var+=(22332323222324242)
var+=("")
var+=("")
for i in "${!var[#]}"
do
[[ "${var[$i]}" ]] || var[$i]="None"
echo "Index: $i - Value: ${var[$i]}"
done
Consider using an array instead of numbered variables:
#!/bin/bash
var[1]=""
var[2]="1233123213"
var[3]="22332323222324242"
var[4]=""
var[5]=""
for i in 1 2 3 4 5
do
if [ -z "${var[i]}" ]
then
var[i]="None"
fi
echo "${var[i]} \r"
done

Conditional statement not working as expected

I am using Konsole on kubuntu 14.04.
I want to take arguments to this shell-script, and pass it to a command. The code is basically an infinite loop, and I want one of the arguments to the inner command to be increased once every 3 iterations of the loop. Ignoring the actual details, here's a gist of my code:
#!/bin/bash
ct=0
begin=$1
while :
do
echo "give: $begin as argument to the command"
#actual command
ct=$((ct+1))
if [ $ct%3==0 ]; then
begin=$(($begin+1))
fi
done
I am expecting the begin variable to be increased every 3 iterations, but it is increasing in the every iteration of the loop. What am I doing wrong?
You want to test with
if [ $(expr $cr % 3) = 0 ]; then ...
because this
[ $ct%3==0 ]
tests whether the string $ct%3==0, after parameter substitution, is not empty. A good way for understanding this is reading the manual for test and look at the semantics when it is given 1, 2, 3 or more arguments. In your original script, it only sees one argument, in mine it sees three. White space is very important in the shell. :-)
In BASH you can completely utilize ((...)) and refactor your script like this:
#!/bin/bash
ct=0
begin="$1"
while :
do
echo "give: $begin as argument to the command"
#actual command
(( ct++ % 3 == 0)) && (( begin++ ))
done

Resources