Say I have the following:
for i in $@; do
echo ${i+1}
done
and I run this on shell $ test.sh 3 5 8 4
, it outputs 1 1 1 1
why wouldn't ${i+1} work? I am trying to access the next argument for a list of command line arguments.
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.
Sign up to join this communitySay I have the following:
for i in $@; do
echo ${i+1}
done
and I run this on shell $ test.sh 3 5 8 4
, it outputs 1 1 1 1
why wouldn't ${i+1} work? I am trying to access the next argument for a list of command line arguments.
Each character in shell may have an special meaning.
The code ${i+1}
does not mean "add 1 to i".
To find what it means, execute this command:
LESS=+/'\{parameter\:\+word\}' man bash
And read:
${parameter:+word}
Use Alternate Value. If parameter is null or unset, nothing is substituted, otherwise the expansion of word is substituted.
And a little way above:
Omitting the colon results in a test only for a parameter that is unset.
As $i
has a value set by the loop for i in $@;
the "Alternate Value" is substituted and 1
is printed.
If you want to add 1 to the value of the arguments, do this:
for i
do echo "$((i+1))"
done
There is no need for the in "$@"
(and get used to quoting all expansions).
$ ./test.sh 3 5 8 4
4
6
9
5
But that is not "the next argument" either. The core issue is with your loop, you are using the value of arguments in a loop, not an index to the arguments. You need to loop over an index i
of the arguments, not the value i
of each argument. Something like:
for (( i=1; i<=$#; i++)); do
echo "${i}"
done
That will print an index, as this shows:
$ ./test.sh 3 5 8 4
1
2
3
4
How do we access the argument at position $i
?: With indirection:
for (( i=1; i<=$#; i++)); do
echo "${!i}"
done
See the simple !
added ?
Now it runs like this:
$ ./test.sh 3 5 8 4
3
5
8
4
And to print both the present argument and the next, use this:
for (( i=1; i<=$#; i++)); do
j=$((i+1))
echo "${!i} ${!j}"
done
No, there is no simpler way than to calculate the value in the variable $j
.
$ ./test.sh 3 5 8 4
3 5
5 8
8 4
4
That works for text also:
$ ./test.sh sa jwe yqs ldfgt
sa jwe
jwe yqs
yqs ldfgt
ldfgt
In your example code i is the value, not the index.
You'll need the exclamation mark for using a variable value as variable.
I didn't get the (i+1) to work without defining another variable. Maybe someone can give a hint to optimize that.
check () {
for i in $(seq $#); do
let j=i+1
echo "$i: i=${!i} i+1=${!j}"
done
}
check a b c
1: i=a i+1=b
2: i=b i+1=c
3: i=c i+1=
First note that, ${i+1}
is an parameter expansion pattern, which means if parameter i
is Not unset or null, it will be substituted by the expansion of 1
, which of course just results in 1
. Hence you are getting all 1's in the output.
You need to use an arithmetic operator, not parameter expansion.
For example:
% check () { for i in "$@"; do echo $((i+1)); done ;}
% check 2 4 5 6
3
5
6
7
i
I find myself using shift for the same purpose. Here's a small example taken from here:
#!/bin/bash
# This script can clean up files that were last accessed over 365 days ago.
USAGE="Usage: $0 dir1 dir2 dir3 ... dirN"
if [ "$#" == "0" ]; then
echo "$USAGE"
exit 1
fi
while (( "$#" )); do
if [[ $(ls "$1") == "" ]]; then
echo "Empty directory, nothing to be done."
else
find "$1" -type f -a -atime +365 -exec rm -i {} \;
fi
shift
Expanding on user79743's answer above:
Suppose we want to have a argument that takes a value like --mode delete
or --mode list
.
When ${!i} == "--mode"
, we want to read the value for the following index (j
or i+1
) and retain it for later. Consider that on the next loop iteration that we will end up inspecting i+1
a second time (was j
on previous loop, is i
on the current loop).
I found that i
is mutable, so we can increment it by 1 meaning that the next loop iteration will be i+2
, so to speak.
I did want to say that, generally speaking, it is unwise to mutate a value while iterating it. This approach did work for me. I'm sure there are probably several (better) ways to do this.
i
. Some indexes are inspected more than once.# $ ./test.sh --mode delete 3 5 8 4
# [1]: --mode [2]: delete
# [2]: delete # <-- index [2] is inspected twice
# [3]: 3
# [4]: 5
# [5]: 8
# [6]: 4
for (( i=1; i<=$#; i++)); do
if [[ ${!i} == "--mode" ]]; then
j=$((i+1))
MODE=${!j} # retain for later
echo "[$i]: ${!i} [$j]: ${!j}"
else
echo "[$i]: ${!i}"
fi
done
i
. Each index is only inspected once.# $ ./test.sh --mode delete 3 5 8 4
# [1]: --mode [2]: delete
# [3]: 3
# [4]: 5
# [5]: 8
# [6]: 4
for (( i=1; i<=$#; i++)); do
if [[ ${!i} == "--mode" ]]; then
j=$((i+1))
MODE=${!j} # retain for later
echo "[$i]: ${!i} [$j]: ${!j}"
i=$i+1 # <-- incrementing the index
else
echo "[$i]: ${!i}"
fi
done