getting positional arguments in bash - bash

I have a bash script called foo with variable number of arguments, with the first one being a required one, i.e.:
foo a1 b2 b3 b4 ...
I understand that in bash $1 will get me argument a1, but is there a way to get all the rest of the arguments? $# or $* seem to get me all the arguments including a1.

Slice the $# array.
echo "${#:2}"

You can use shift command for that. That will remove $1 and you can access the rest of arguments starting with $1.

#!/bin/sh
echo $*
shift
echo $*
shift will shift all the parameters, running the previous example would give:
$ test_shift.sh a b c d e
a b c d e
b c d e

./foo.sh 1 2 3 4
#!/bin/bash
echo $1;
echo $2;
echo $3;
echo $4;
Will output:
1
2
3
4

Related

Can I output multiple value in bash, like read A B C <<< "1 2 3"?

I am learning bash shell.
I found a command "read" which can deliver multiple values to different variables, like
read A B C ... <<< "1 2 3 ..."
Now I make a function
function echo_multiple_values() {
echo "1 2 3 ..."
}
Do I have a smart way to output multiple values from a function, like
A B C ...=$(echo_multiple_values)
Thank you very much.
You can't do
A B C ...=$(echo_multiple_values)
but you may wish to do something like below :
myfun(){
arr=( {1..3} ) #{a..b} is a bash range
echo "${arr[#]}"
}
read a b c <<<"$(myfun)"
Also, you could do something like below
$alphabets=( {a..z} )
$ nums=( {1..26} )
$ read "${alphabets[#]}" <<<"${nums[#]}"
$ echo $a
1
$ echo $c
3
$ echo $z
26

How to fetch last argument and stop before last arguments in shell script?

I want to merge all files into one. Here, the last argument is the destination file name.
I want to take last argument and then in loop stop before last arguments.
Here code is given that I want to implement:
echo "No. of Argument : $#"
for i in $* - 1
do
echo $i
cat $i >> last argument(file)
done
How to achieve that?
Using bash:
fname=${!#}
for a in "${#:1:$# - 1}"
do
echo "$a"
cat "$a" >>"$fname"
done
In bash, the last argument to a script is ${!#}. So, that is where we get the file name.
bash also allows selecting elements from an array. To start with a simple example, observe:
$ set -- a b c d e f
$ echo "${#}"
a b c d e f
$ echo "${#:2:4}"
b c d e
In our case, we want to select elements from the first to the second to last. The first is number 1. The last is number $#. We want to select all but the last. WE thus want $# - 1 elements of the array. Therefore, to select the arguments from the first to the second to last, we use:
${#:1:$# - 1}
A POSIX-compliant method:
eval last_arg=\$$#
while [ $# -ne 1 ]; do
echo "$1"
cat "$1" >> "$last_arg"
shift
done
Here, eval is safe, because you are only expanding a read-only parameter in the string that eval will execute. If you don't want to unset the positional parameters via shift, you can iterate over them, using a counter to break out of the loop early.
eval last_arg=\$$#
i=1
for arg in "$#"; do
echo "$arg"
cat "$arg" >> "$last_arg"
i=$((i+1))
if [ "$i" = "$#" ]; then
break
fi
done

Need to printf script's arguments

I'm trying to list script's argument by printf function. Arguments are counted by iteration of $i. What should be in printf function?
I need something like
eval echo \$$i
but in printf function.
Edit: Have while cycle with iteration of $i and among other code, I have
printf "%s" $i
But, instead of $i, I need some code, that shows me value of argument.
In my case, it is name of file, and I need list them. One file in one iteration.
As noted in a comment, you normally do that with a loop such as:
i=1
for arg in "$#"
do
echo "$i: [$arg]"
((i++))
done
(If echo isn't allowed, use printf "%s\n" … where the … is whatever would have followed echo.)
You might also use indirect expansion to avoid the use of eval:
for i in 1 2 3 4; do echo "$i: [${!i}]"; done
You can generalize that with:
for i in $(seq 1 $#); do echo "$i: [${!i}]"; done
or
for ((i = 1; i <= $#; i++)); do echo "$i: [${!i}]"; done
For example, given:
set -- a b 'c d' ' e f '
all the loops produce the output:
1: [a]
2: [b]
3: [c d]
4: [ e f ]
The square brackets are merely there to delimit the argument values; it allows you to see the trailing blanks on the fourth line of output.
You might also be able to use:
printf "[%s]\n" "$#"
to get:
[a]
[b]
[c d]
[ e f ]
It is not very clear what you are asking for, but this will list the arguments passed to the script:
while [ $# -gt 0 ]; do
printf "%s\n" "$1"
shift
done
I guess what you are asking for is how to make the indirect reference to positional parameter i (i containing the position):
print "%s\n" ${!i}
To get your arguments in such a way that they can be fed back into the shell with the exact same value, the following bash extension can be used:
printf '%q ' "$#"; printf '\n'
This works even for rather unusual cases. Let's say that one of your arguments contains a newline:
./your-script 'hello
world' 'goodbye world'
This will be represented in the printf output:
$'hello\nworld' goodbye\ world
...with something you can use again in the shell:
$ echo $'hello\nworld' goodbye\ world
hello
world goodbye world

bash get list length when list name is not fixed

Suppose I have two lists:
lista="a b c d"
listb="e f"
I would like to write a function that returns the number of items on a given list:
>>foo $lista
4
>>foo $listb
2
I've tried using ${#<varname>[#]} syntax, also ${#!<varname>[#]}, unsuccessfully.
Thanks
You can use wc -w for this:
$ lista="a b c d"
$ wc -w <<< "$lista"
4
$ listb="e f"
$ wc -w <<< "$listb"
2
From man wc:
-w, --words
print the word counts
To make it function, use:
list_length () {
echo $(wc -w <<< "$#")
}
And then you can call it like:
list_length "a b c"
Put them into BASH array and look at array length.
a=($lista)
echo ${#a[#]}
4
a=($listb)
echo ${#a[#]}
2
If you indeed want to write a function, you can take advantage of normal parameter parsing and the fact that $# contains the number of parameters passed:
foo() { echo $#; }

looping over arguments in bash

In bash, I can loop over all arguments, $#.
Is there a way to get the index of the current argument? (So that I can refer to the next one or the previous one.)
You can loop over the argument numbers, and use indirect expansion (${!argnum})to get the arguments from that:
for ((i=1; i<=$#; i++)); do
next=$((i+1))
prev=$((i-1))
echo "Arg #$i='${!i}', prev='${!prev}', next='${!next}'"
done
Note that $0 (the "previous" argument to $1) will be something like "-bash", while the "next" argument after the last will come out blank.
Not exactly as you specify, but you can iterate over the arguments a number of different ways.
For example:
while test $# -gt 0
do
echo $1
shift
done
Pretty simple to copy the positional params to an array:
$ set -- a b c d e # set some positional parameters
$ args=("$#") # copy them into an array
$ echo ${args[1]} # as we see, it's zero-based indexing
b
And, iterating:
$ for ((i=0; i<${#args[#]}; i++)); do
echo "$i ${args[i]} ${args[i-1]} ${args[i+1]}"
done
0 a e b
1 b a c
2 c b d
3 d c e
4 e d

Resources