tags:

views:

56

answers:

3

I want to parse the arguments given to a shell script by using a for-loop. Now, assuming I have 3 arguments, something like for i in $1 $2 $3 should do the job, but I cannot predict the number of arguments, so I wanted use an RegEx for the range and $# as the number of the last argument. I don't know how to use these RegEx' in a for-loop, I tried something like for i in $[1-$#] which doesn't work. The loop only runs 1 time and 1-$# is being calculated, not used as a RegEx.

+6  A: 
John Kugelman
pretty simple, thanks.
default-user
The Bash builtin `getopts` [is preferable](http://mywiki.wooledge.org/BashFAQ/035) to the external `getopt` since the latter "can't handle empty arguments strings, or arguments with embedded whitespace."
Dennis Williamson
@Dennis That's not true nowadays. See `man getopt` to see if you have the newer enhanced version that plugs these holes: "Traditional implementations of getopt(1) are unable to cope with whitespace and other (shell-specific) special characters in arguments and non-option parameters. To solve this problem, this implementation can generate quoted output which must once again be interpreted by the shell..."
John Kugelman
If backwards compatibility is important you can use the `-T` flag to test if your `getopt` is enhanced. It will return exit status 4 if it is; older `getopt`s will exit with 0.
John Kugelman
@John: Thanks for that information. I hadn't noticed that in the man page.
Dennis Williamson
@Dennis I was quite confused the first time I read that FAQ entry. The language couldn't be any stronger: **"Never use getopt(1)."** In bold. "Please forget that it ever existed." It would be really nice if the FAQ qualified that advice with *unless you have GNU's `getopt`, which is perfectly fine!*
John Kugelman
A: 

I suggest doing something else instead:

while [ -n "$1" ] ; do
  # Do something with $1
  shift
  # Now whatever was in $2 is now in $1
done

The shift keyword moves the content of $2 into $1, $3 into $2, etc. pp.

Let's say the arguments where:

a b c d

After a shift, the arguments are now:

b c d

With the while loop, you can thus parse an arbitrary number of arguments and can even do things like:

while [ -n "$1" ] ; do
  if [ "$1" = "-f" ] ; then
    shift
    if [ -n "$1" ] ; then
      myfile="$1"
    else
      echo "-f needs an additional argument"
    end
  fi
  shift
done

Imagine the arguments as being an array and $n being indexes into that array. shift removes the first element, so the index 1 now references the element that was at index 2 prior to shift. I hope you understand what I want to say.

DarkDust
That's a hard way of writing a simple `for` loop which does the job cleanly - no need to use shift, for example.
Jonathan Leffler
I see, this should be a way better approach to parse arguments in general. Thanks, I'll use it when I have a few more arguments.
default-user
@Jonathan Leffler: Yes, the `for arg in "$@"` is easier but it's not as flexible if you're doing "serious" argument parsing... you would have to store intermediate values in temporary variables which then makes the whole job harder than with the while loop.
DarkDust
That while loop may terminate too early if the user actually passes an empty string argument: `a b "" d`
glenn jackman
To avoid the problem that @glenn pointed out, you can use `while (( $# > 0 ))` or `while [ $# -gt 0 ]`.
Dennis Williamson
+1  A: 

Another way to iterate over the arguments which is closer to what you were working toward would be something like:

for ((i=1; i<=$#; i++))
do
    echo "${@:i:1}"
done

but the for arg syntax that John Kugelman showed is by far preferable. There are, however, times when array slicing is useful. Also, in this version, as in John's, the argument array is left intact. Using shift discards its elements.

You should note that what you were trying to do with square brackets is not a regular expression at all.

Dennis Williamson