3

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.

  • What do you mean next argument? Do you mean if gave 3, then you want 4? – cuonglm Feb 10 '16 at 6:57
4

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

Next argument.

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

Indirection

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

Final solution.

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 
0

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
  • 1
    hi, I meant to ask how to access the next/previous argument using just i – 夢のの夢 Feb 10 '16 at 6:05
0

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
0

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=

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.