How do I access arguments to functions if there are more than 9 arguments? - bash

With first 9 arguments being referred from $1-$9, $10 gets interpreted as $1 followed by a 0. How do I account for this and access arguments to functions greater than 10?
Thanks.

Use :
#!/bin/bash
echo ${10}
To test the difference with $10, code in foo.sh :
#!/bin/bash
echo $10
echo ${10}
Then :
$ ./foo.sh first 2 3 4 5 6 7 8 9 10
first0
10
the same thing is true if you have :
foobar=42
foo=FOO
echo $foobar # echoes 42
echo ${foo}bar # echoes FOObar
Use {} when you want to remove ambiguities ...
my2c

If you are using bash, then you can use ${10}.
${...} syntax seems to be POSIX-compliant in this particular case, but it might be preferable to use the command shift like that :
while [ "$*" != "" ]; do
echo "Arg: $1"
shift
done
EDIT: I noticed I didn't explain what shift does. It just shift the arguments of the script (or function). Example:
> cat script.sh
echo "$1"
shift
echo "$1"
> ./script.sh "first arg" "second arg"
first arg
second arg
In case it can help, here is an example with getopt/shift :
while getopts a:bc OPT; do
case "$OPT" in
'a')
ADD=1
ADD_OPT="$OPTARG"
;;
'b')
BULK=1
;;
'c')
CHECK=1
;;
esac
done
shift $( expr $OPTIND - 1 )
FILE="$1"

In general, to be safe that the whole of a given string is used for the variable name when Bash is interpreting the code, you need to enclose it in braces: ${10}

Related

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

Bash: handling mass arguments

I'd like to be able to handle multiple arguments to a given flag no matter what the order of flags is. Do you guys think this is acceptable? Any improvements?
So:
$ ./script -c opt1 opt2 opt3 -b foo
opt1 opt2 opt3
foo
Code:
echo_args () {
echo "$#"
}
while (( $# > 0 )); do
case "$1" in
-b)
echo $2
;;
-c|--create)
c_args=()
# start looping from this flag
for arg in ${#:2}; do
[ "${arg:0:1}" == "-" ] && break
c_args+=("$arg")
done
echo_args "${c_args[#]}"
;;
*)
echo "huh?"
;;
esac
shift 1
done
The getopts utility shall retrieve options and option-arguments from a list of parameters.
$ cat script.sh
cflag=
bflag=
while getopts c:b: name
do
case $name in
b) bflag=1
bval="$OPTARG";;
c) cflag=1
cval="$OPTARG";;
?) printf "Usage: %s: [-c value] [-b value] args\n" $0
exit 2;;
esac
done
if [ ! -z "$bflag" ]; then
printf 'Option -b "%s" specified\n' "$bval"
fi
if [ ! -z "$cflag" ]; then
printf 'Option -c "%s" specified\n' "$cval"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: %s\n" "$*"
Note the Guideline 8:
When multiple option-arguments are specified to follow a single option, they should be presented as a single argument, using commas within that argument or <blank>s within that argument to separate them.
$ ./script.sh -c "opt1 opt2 opt3" -b foo
Option -b "foo" specified
Option -c "opt1 opt2 opt3" specified
Remaining arguments are:
The standard links are listed below:
getopts - parse utility options
Section 12.2 Utility Syntax Guidelines
I noticed in the comments that you don't want to use any of these. What you could do is set all of the arguments as a string, then sort them using a loop, pulling out the ones you want to set as switched and sorting them using if statements. It is a little brutish, but it can be done.
#!/bin/bash
#set all of the arguments as a variable
ARGUMENTS=$#
# Look at each argument and determine what to do with it.
for i in $ARGUMENTS; do
# If the previous loop was -b then grab the value of this argument
if [[ "$bgrab" == "1" ]]; then
#adds the value of -b to the b string
bval="$bval $i"
bgrab="0"
else
# If this argument is -b, prepare to grab the next argument and assign it
if [[ "$i" == "-b" ]]; then
bgrab="1"
else
#Collect the remaining arguments into one list per your example
RemainingArgs="$RemainingArgs $i"
fi
fi
done
echo "Arguments: $RemainingArgs"
echo "B Value: $bval"
I use something similar in a lot of my scripts because there are a significant amount of arguments that can be fed into some of them, and the script needs to look at each one to figure out what to do. They can be out of order or not exist at all and the code still has to work.

Command line arguments in shell script

Following is the shell script which iterates over command line arguments and prints the values
for var in "$#"
do
echo $var
done
Now if i want to iterate from the second command line argument (the first argument being used for some other purpose), what is the command to exclude the first argument alone in iteration ?
Use shift:
#!/usr/bin/env bash
shift
for var in "$#"; do
echo "$var"
done
The first argument is in $1, so doing
var1=$1
will store it into var1
You can then use shift to "delete" the first arg and still use your for loop:
~$cat s.sh
var1=$1
shift
echo var1=$var1
for var in "$#"
do
echo $var
done
~$ ./s.sh 1 2 3
var1=1
2
3
From man bash:
shift [n]
The positional parameters from n+1 ... are renamed to $1 .... Parameters
represented by the numbers $# down to $#-n+1 are unset. n
must be a non-negative number less than or equal to $#. If n
is 0, no parameters are changed. If n is not given, it is assumed to be 1. If n is greater than $#, the positional parameters
are not changed. The return status is greater than zero if n is
greater than $# or less than zero; otherwise 0.
This example behaves more like a standard unix command line utility. Options are processed in any order and can have modifiers. It does not work for every situation (like modifiers with spaces in them), but I've found it to be very useful for scripts that I want to have some default behavior, but occasionally I want to modify one or more parameters.
#!/bin/bash
#defaults
VAR1="Yours"
VAR2="Mine"
VAR3="Ours"
SHARENICE="false"
while [ $# -gt 0 ]; do
case "$1" in
#single parameter
-s) SHARENICE="true"
shift
;;
#modified parameters
-1) VAR1="$2"
shift 2
;;
-2) VAR2="$2"
shift 2
;;
-3) VAR3="$2"
shift 2
;;
#you could put a 'usage' function here,
#or if your last parameter has no modifier,
#like a mandatory input, it will now be at
#$1
*)
break
;;
esac
done
echo -n "$VAR1, $VAR2 and $VAR3. "
if [ "$SHARENICE" != "true" ]; then
echo "Too bad we don't get along."
else
echo "Good thing we play nice!"
fi

Extract parameters before last parameter in "$#"

I'm trying to create a Bash script that will extract the last parameter given from the command line into a variable to be used elsewhere. Here's the script I'm working on:
#!/bin/bash
# compact - archive and compact file/folder(s)
eval LAST=\$$#
FILES="$#"
NAME=$LAST
# Usage - display usage if no parameters are given
if [[ -z $NAME ]]; then
echo "compact <file> <folder>... <compressed-name>.tar.gz"
exit
fi
# Check if an archive name has been given
if [[ -f $NAME ]]; then
echo "File exists or you forgot to enter a filename. Exiting."
exit
fi
tar -czvpf "$NAME".tar.gz $FILES
Since the first parameters could be of any number, I have to find a way to extract the last parameter, (e.g. compact file.a file.b file.d files-a-b-d.tar.gz). As it is now the archive name will be included in the files to compact. Is there a way to do this?
To remove the last item from the array you could use something like this:
#!/bin/bash
length=$(($#-1))
array=${#:1:$length}
echo $array
Even shorter way:
array=${#:1:$#-1}
But arays are a Bashism, try avoid using them :(.
Portable and compact solutions
This is how I do in my scripts
last=${#:$#} # last parameter
other=${*%${!#}} # all parameters except the last
EDIT
According to some comments (see below), this solution is more portable than others.
Please read Michael Dimmitt's commentary for an explanation of how it works.
last_arg="${!#}"
Several solutions have already been posted; however I would advise restructuring your script so that the archive name is the first parameter rather than the last. Then it's really simple, since you can use the shift builtin to remove the first parameter:
ARCHIVENAME="$1"
shift
# Now "$#" contains all of the arguments except for the first
Thanks guys, got it done, heres the final bash script:
#!/bin/bash
# compact - archive and compress file/folder(s)
# Extract archive filename for variable
ARCHIVENAME="${!#}"
# Remove archive filename for file/folder list to backup
length=$(($#-1))
FILES=${#:1:$length}
# Usage - display usage if no parameters are given
if [[ -z $# ]]; then
echo "compact <file> <folder>... <compressed-name>.tar.gz"
exit
fi
# Tar the files, name archive after last file/folder if no name given
if [[ ! -f $ARCHIVENAME ]]; then
tar -czvpf "$ARCHIVENAME".tar.gz $FILES; else
tar -czvpf "$ARCHIVENAME".tar.gz "$#"
fi
Just dropping the length variable used in Krzysztof Klimonda's solution:
(
set -- 1 2 3 4 5
echo "${#:1:($#-1)}" # 1 2 3 4
echo "${#:(-$#):($#-1)}" # 1 2 3 4
)
I would add this as a comment, but don't have enough reputation and the answer got a bit longer anyway. Hope it doesn't mind.
As #func stated:
last_arg="${!#}"
How it works:
${!PARAM} indicates level of indirection. You are not referencing PARAM itself, but the value stored in PARAM ( think of PARAM as pointer to value ).
${#} expands to the number of parameters (Note: $0 - the script name - is not counted here).
Consider following execution:
$./myscript.sh p1 p2 p3
And in the myscript.sh
#!/bin/bash
echo "Number of params: ${#}" # 3
echo "Last parameter using '\${!#}': ${!#}" # p3
echo "Last parameter by evaluating positional value: $(eval LASTP='$'${#} ; echo $LASTP)" # p3
Hence you can think of ${!#} as a shortcut for the above eval usage, which does exactly the approach described above - evaluates the value stored in the given parameter, here the parameter is 3 and holds the positional argument $3
Now if you want all the params except the last one, you can use substring removal ${PARAM%PATTERN} where % sign means 'remove the shortest matching pattern from the end of the string'.
Hence in our script:
echo "Every parameter except the last one: ${*%${!#}}"
You can read something in here: Parameter expansion
Are you sure this fancy script is any better than a simple alias to tar?
alias compact="tar -czvpf"
Usage is:
compact ARCHIVENAME FILES...
Where FILES can be file1 file2 or globs like *.html
Try:
if [ "$#" -gt '0' ]; then
/bin/echo "${!#}" "${#:1:$(($# - 1))}
fi
Array without last parameter:
array=${#:1:$#-1}
But it's a bashism :(. Proper solutions would involve shift and adding into variable as others use.
#!/bin/bash
lastidx=$#
lastidx=`expr $lastidx - 1`
eval last='$'{$lastidx}
echo $last
Alternative way to pull the last parameter out of the argument list:
eval last="\$$#"
eval set -- `awk 'BEGIN{for(i=1;i<'$#';i++) printf " \"$%d\"",i;}'`
#!/bin/sh
eval last='$'$#
while test $# -gt 1; do
list="$list $1"
shift
done
echo $list $last
I can't find a way to use array-subscript notation on $#, so this is the best I can do:
#!/bin/bash
args=("$#")
echo "${args[$(($#-1))]}"
This script may work for you - it returns a subrange of the arguments, and can be called from another script.
Examples of it running:
$ args_get_range 2 -2 y a b "c 1" d e f g
'b' 'c 1' 'd' 'e'
$ args_get_range 1 2 n arg1 arg2
arg1 arg2
$ args_get_range 2 -2 y arg1 arg2 arg3 "arg 4" arg5
'arg2' 'arg3'
$ args_get_range 2 -1 y arg1 arg2 arg3 "arg 4" arg5
'arg2' 'arg3' 'arg 4'
# You could use this in another script of course
# by calling it like so, which puts all
# args except the last one into a new variable
# called NEW_ARGS
NEW_ARGS=$(args_get_range 1 -1 y "$#")
args_get_range.sh
#!/usr/bin/env bash
function show_help()
{
IT="
Extracts a range of arguments from passed in args
and returns them quoted or not quoted.
usage: START END QUOTED ARG1 {ARG2} ...
e.g.
# extract args 2-3
$ args_get_range.sh 2 3 n arg1 arg2 arg3
arg2 arg3
# extract all args from 2 to one before the last argument
$ args_get_range.sh 2 -1 n arg1 arg2 arg3 arg4 arg5
arg2 arg3 arg4
# extract all args from 2 to 3, quoting them in the response
$ args_get_range.sh 2 3 y arg1 arg2 arg3 arg4 arg5
'arg2' 'arg3'
# You could use this in another script of course
# by calling it like so, which puts all
# args except the last one into a new variable
# called NEW_ARGS
NEW_ARGS=\$(args_get_range.sh 1 -1 \"\$#\")
"
echo "$IT"
exit
}
if [ "$1" == "help" ]
then
show_help
fi
if [ $# -lt 3 ]
then
show_help
fi
START=$1
END=$2
QUOTED=$3
shift;
shift;
shift;
if [ $# -eq 0 ]
then
echo "Please supply a folder name"
exit;
fi
# If end is a negative, it means relative
# to the last argument.
if [ $END -lt 0 ]
then
END=$(($#+$END))
fi
ARGS=""
COUNT=$(($START-1))
for i in "${#:$START}"
do
COUNT=$((COUNT+1))
if [ "$QUOTED" == "y" ]
then
ARGS="$ARGS '$i'"
else
ARGS="$ARGS $i"
fi
if [ $COUNT -eq $END ]
then
echo $ARGS
exit;
fi
done
echo $ARGS
This works for me, with sh and bash:
last=${*##* }
others=${*%${*##* }}

bash script parameters [duplicate]

This question already has answers here:
How do I parse command line arguments in Bash?
(40 answers)
Closed 6 years ago.
I need to write a bash script, and would like it to parse unordered parameters of the format:
scriptname --param1 <string> --param2 <string> --param3 <date>
Is there a simple way to accomplish this, or am I pretty much stuck with $1, $2, $3?
You want getopts.
while [[ $1 = -* ]]; do
arg=$1; shift # shift the found arg away.
case $arg in
--foo)
do_foo "$1"
shift # foo takes an arg, needs an extra shift
;;
--bar)
do_bar # bar takes no arg, doesn't need an extra shift
;;
esac
done
A nice example of how to implement short & long switches side by side is mcurl:
http://www.goforlinux.de/scripts/mcurl/
Bash has a getops function, as mentioned here before, that might solve your problems.
If you need anything more sophisticated, bash also supports positional parameters (ordered $1 ... $9, and then ${10} .... ${n}), you'll have to come up with your own logic to handle this input. One easy way to go is to put a switch/case inside of a for loop, iterating over the parameters. You can use either one of the two special bash vars that handle the input: $* or $#.
#!/bin/bash
# Parse the command-line arguments
while [ "$#" -gt "0" ]; do
case "$1" in
-p1|--param1)
PARAM1="$2"
shift 2
;;
-p2|--param2)
PARAM2="$2"
shift 2
;;
-p3|--param3)
PARAM3="$2"
shift 2
;;
-*|--*)
# Unknown option found
echo "Unknown option $1."
exit 1
;;
*)
CMD="$1"
break
;;
esac
done
echo "param1: $PARAM1, param2: $PARAM2, param3: $PARAM3, cmd: $CMD"
When I execute this:
./<my-script> --param2 my-param-2 --param1 myparam1 --param3 param-3 my-command
it outputs what you expect:
param1: myparam1, param2: my-param-2, param3: param-3, cmd: my-command

Resources