shell getopts: opt w/ optional parameter is taking next opt as its parameter!
This is the first time ever, I'm trying to use `getopt(s)` in a shell script (figured I should eventually, some day, learn how to use it). I'm only doing short (-a -b) options for now; once that works, I'll deal with --long options.
I'm using manpages for reference, as well as: http://tldp.org/LDP/abs/html/internal.html#EX33 And tell you what, that manpage could use some examples! Anyhow, while I question whether there's really anything to gain by using getopts over manually dealing with options (which has been working just fine, thanks), it's basically so far so good... Except: like the title says, I have one option which can take a (optional) parameter to it, and it's screwing up the getopts parsing. First, my getopts code: Code:
while getopts ":l.#vhAabcdkpPswzR:" OPT; do The (temporary) butchery code I have in the case/esac for R) was to see if an arg to -R was given, and if it seemed to be an integer. That code doesn't work and is crappy. Here's some sample runs of the program, demonstrating the problem: This is GOOD: Code:
root@reactor: sl -R2 -lA# /etc Code:
root@reactor: sl -lA# -R2 /etc Code:
root@reactor: sl -lA# -R /etc Code:
root@reactor: sl -RlA# /etc Code:
root@reactor: sl -R2lA# /etc So... I can manually sanity-check to see that, if -R was given, it has a proper (integer) arg to it, and if not, ignore it, HOWEVER, by that point, getopts has already wrongly sucked up the remaining opts passed to the program (those that followed -R), and so getopts's done processing opts as far as it's concerned, and the rest of the args are discarded.. What to do? The 3 "BAD" examples given above, should be "GOOD" if possible. For the record: 1) this is Bash shell at this time, but will be Dash ultimately. 2) /bin/getopt doesn't work like this one - I can't make it work at all. Does it ever work? Should I be using that instead? Thanks for your insight, folks! EDIT: For clarity (in case I've been confusing above): Code:
root@reactor: sl -ls#R2 /etc/profile.d Code:
root@reactor: sl -lR2s# /etc/profile.d Code:
sl -l -R2 -s -# /etc/profile.d |
I would say it is working exactly as it should :)
Any option that you tell to have optional arguments after will take whatever is next on the command line. If you ran: Code:
sl -lA# -R Quote:
Not sure if that helps, but it appears whatever is next will always be drawn in. |
Hi grail,
thanks for the input; I don't think it helped though. To demonstrate that a [space] doesn't help here: Code:
root@reactor: sl -l -R 2 -s /etc/profile.d Code:
root@reactor: sl -l -R -s /etc/profile.d In the second example, R should be happy with no value, because a value is optional; it should not take -s as the value of -R. Basically what I'm looking for here in getopts, is: if the character(s) immediately following -R (possibly preceeded by one or more [space]'s) are digit(s) then they are the value for -R. But if non-numeric character(s) (possibly preceeded by 1+ [spaces]) follow -R, then give -R no value, and continue with parsing opts beginning with the character following R. Is this (more) clear? :) Maybe what I should be doing, is pre-parsing my ${@} before the getopts, to isolate/separate the -R situation, and create a new string of opts/args and then use getopts on that new string (can I use getopts on a string rather than an array? I think so..) but this-all seems to defeat the purpose of using getopts to begin with. :scratch: |
Hi GrapefruiTgirl,
one possible way to deal with optional parameters is to pass them like Code:
script -R=-5 filename We can test for a space after -R by utilising $OPTIND. Example: Code:
#!/bin/bash Let me know if this example clears things up a bit. If not I'll try to be more verbose. [EDIT] I changed Code:
echo "OPTIND is now: $((OPTIND--))" # decrease OPTIND so that the Code:
echo "OPTIND is now: $((--OPTIND))" # decrease OPTIND so that the |
I understand you perfectly, but I think I am not explaining very well :(
Both examples you just gave are demonstrating what I meant about the space. That is on the consecutive characters, be it the single 2 or both -s characters, are being used as optional argument. So nothing else is being drawn in is what I meant. Hope that is clearer. I think part of your issue is that although it says 'optional' argument, it actually means that you can but don't have to put anything after, but if you do put anything after it it will be considered as the argument for -R. I played with changing the OPTIND variable and had both success and failure. If you change your -R option to the following: Code:
R) [ "$OPTARG" -a $((OPTARG)) -ge 1 ] && MAXDEPTH="-maxdepth $OPTARG" || {unset MAXDEPTH;((OPTIND--));} ;; The downside to this is that if you enter the following: Code:
sl -RlA Also, on a side note, having a check for OPTARG has a value at all is not required as by default no argument would make -R slip to your default. |
Hmmm ... so crts, why do I get an infinite loop but yours seems to work ok??
|
Also I just found that if I enter:
Code:
sl -R2 On the plus side -2R works :) |
Quote:
Code:
script -R=option -2 Code:
script -Roption -2 So Code:
script -R2 Code:
script -R=2 Or you could try to handle that parameter in the '-R' branch. |
Quote:
Code:
sl -Rla |
minor change in post #4. Corrected false output!
|
So I am running really vague here this morning :(
Why does your code decrement OPTIND and not cause an infinite loop and mine does? I know the answer will be simple but it is eluding me :( GGirl .. sorry to hijack this thread a little .. just trying to wrap my head around it :) |
Part 1
Quote:
Ok, I'll give another try. Basically, getopts simply parses the positional parameters (PP); just like any script does. It uses its internal OPTIND variable to keep track which PP has to be parsed next. Let's have a look at this example: Code:
while getopts ":ab:c:d" OPT; do Code:
script -a -b ArgumentToB -cArgumentToC -d filename First iteration OPTIND has the value 1. It gets the PP $1 by internally evaluating ${OPTIND}, which results to ${1} and finally to '-a'. It strips the '-' and stores 'OPT=a'. Since option 'a' was declared to take NO argument, getopts internally increases: OPTIND=OPTIND+1 Now OPTIND has the value 2. At this point our code inside the while-loop will be executed, with OPTIND not pointing to the current PP, but to the next one! II) "-b ArgumentToB" In the next iteration getopts will fetch PP $2. PP $2 is '-b' in our example. However, 'b' was declared to come with an argument. So getopts analyzes the length of PP $2. In this case the length of '-b' is 2. For getopts this means, that the argument to option 'b' must be stored in PP $3. So again it strips away the leading '-' of $2 and stores OPT=b. In addition it fetches $3, which is 'ArgumentToB', and stores it into OPTARG. So in order to get the next option (which is stored in PP $4) in the next iteration of the while-loop OPTIND has to be increased: OPTIND=OPTIND+2 Our code is executed with OPTIND pointing to PP $4. III) "-cArgumentToC" Now in the next loop OPTIND has the value of 4. So it fetches PP $4. $4 is '-cArgumentToC' in our example. The option is the first character after the '-', 'c' in this case. This option also has been declared to have an argument. getopts analyzes the length '-cArgumentToC' and this time it results to something greater than 2. Now getopts 'knows' that the argument is also stored in the same PP and NOT in the next one, as it was previously with '-b'. getopts strips the '-', stores the first character into OPT and the rest of $4 into OPTARG. Since option AND argument were stored in the same PP, getopts increases: OPTIND=OPTIND+1 OPTIND has now the value of 5 and in the next iteration it will get PP $5 and thus process '-d'. Now, let us have a look at this Code:
script -cad filename ... to be continued |
Part 2
... continuation
The headaches start when you want to handle options, which take an optional argument. Since getopts won't do it for us, we have to take care of it ourselves. So let us make 'c' have an optional argument. We want to call our script like Code:
script -c -d -a filename So when we step into the loop, the situation is as follows: Code:
OPT=c Code:
script -c[=optional] # the '=' sign is for readability! Code:
if eval [[ "$"$((OPTIND-1))"" == "$OPTARG" ]];then # I know, it's ugly Code:
while getopts ":ab:c:d" OPT; do Code:
script -c=ArgumentToC -d filename Code:
OPT=c Code:
if eval [[ "$"$((OPTIND-1))"" == "$OPTARG" ]] I hope this clears things up a bit. I know, I did not cover Code:
script -ad filename # this is valid Now one might think to check if OPTARG start with a '-'. But then you would not be able to pass, e.g. negative integers or generally arguments that start with '-'. So it is not valid as a general procedure. As for the infinite loop issue: It is situation III) all over again. Check what getopts does with PP $1, OPTIND, OPTARG and then what the code does with OPTIND etc. The condition can only evaluate to false. |
Quote:
Code:
script -cd filename # this is valid Thank you very much for the explanation as it did clear up some things for me. If I am understanding and we use the above second example we get: Code:
OPT=c the above to be wrong (from the point of view not what we want) Is there then a way to dispense with the '-c' and have our script now process '-d' which is also part of OPTIND=1? Or is the solution here to just present the user with an error as '-c' must take an argument and we want it to be numeric? I hope I too am making myself clear as i am trying not to over complicate. |
Quote:
Code:
script -a -d filename Code:
script -ad filename Quote:
Quote:
Code:
script -cda filename Quote:
Which leaves us with Quote:
|
All times are GMT -5. The time now is 10:10 AM. |