0

I have a file f1 with contents:

james john joe  
marie james  
joe   
don marie

I want to output the occurrence of the command line arguments in each line using grep in a shell script s1.sh with content:

#!/bin/bash   
for ((i = $# ; i > 0 ; i-- ))  
    do  
        grep -c $i f1  
    done

Say I execute it as follows:

s1.sh james joe

How do I make the script interpret the variable i as the positional parameter?

Kusalananda
  • 333,661
Jay931
  • 1

2 Answers2

2

If you want to use the script's argument, one at a time, in calls to grep, you don't need to do an arithmetic for loop:

#!/bin/sh

for pattern do grep -c -e "$pattern" f1 done

This for loop loops over the positional parameters, i.e. the argument to your script. For each iteration, the value $pattern will be the next command-line argument.

I've also used -e with grep above to ensure that a pattern that starts with a dash is not mistakenly interpreted as an option to grep. I have also quoted the pattern to avoid performing word splitting and filename globbing.

If the pattern is in fact a string and not a regular expression, then consider using grep with -F.

The list of positional parameters is available as "$@", and the above loop implicitly uses this. To loop with "$@", use

for pattern in "$@"; do
   ...
done

Note that the quoting is essential, or you'll perform word splitting and filename globbing on the script's arguments.

No piece of shell code in this answer requires bash, so I'm using /bin/sh in the #!-line.

Kusalananda
  • 333,661
1

How do I make the script interpret the variable i as the positional parameter?

Probably the easiest way to get the positional parameter indexed by a variable in Bash is to apply the array slice notation to $@. "${@:n:k}" expands to the positional parameters from n to n+k-1, just like "${array[@]:n:k}" expands to the array elements from "${array[n]}" to "${array[n+k-1]}". n and k are evaluated using shell arithmetic, so you can do e.g. "${@:a+b-1:1}". Just "${@:i:1}" gives the one parameter at position i.

Alternatively, the indirect expansion ${!var} does deal with the positional parameters too, so e.g. with i=2, "${!i}" would give "$2". Well, that's shorter than the above, too. On the other hand, namerefs don't work with the positional parameters, declare -n ref=2 gives an error...

See e.g. Does bash provide support for using pointers?

Another way is to copy the list of positional parameters to a named array, and then index that in the usual manner. args=("$@") to do the copy and then use "${args[i]}". Again, the index here is an arithmetic expansion.


Of course, if you're happy to access the positional parameters in ascending order, as is usual, and don't actually need the reverse order which you show, then just use for x in "$@"; do ... or for x do ... as in Kusalanda's answer.

But you could also utilize the same looping construct to invert the list of positional parameters first. E.g. this would print dd cc bb aa, even in plain sh without namers, indirect references or arrays:

set -- aa bb cc dd 
first=1
for x in "$@"; do
    # on first iteration, clear the list
    [ "$first" = 1 ] && set --  
    first=
    set -- "$x" "$@" 
done
echo "$@"
ilkkachu
  • 138,973