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:
|
Quote:
I'm still pondering this and reading (and appreciating) all input in this situ so please feel free to continue discussions. (I still have yet to read all that has been added to the thread since yesterday.) |
@crts - thanks again as I agree that the trouble to process the single 'like to have' option is far outweighed by the level of difficulty to make it happen :)
@GGirl - thanks for being patient :) I believe crts' solution to include equals is the best option and that it allows you to then formulate an appropriate response should the user not do so. I look forward to seeing which way you choose :) |
nice input for getopts
This isn't likely my final post to this thread. I have yet to re-read all the input provided above; but on what I have read, and on my experimenting, I conclude that shell getopts doesn't do what I want in every case.
I've tried a bunch of workarounds, including about 10 lines of sed expressions and a bunch of other crap to pre-process the CLI args, but it was still not working right and getting ridiculous. I have what I think is the solution, but it won't necessarily work *exactly* like this for someone with slightly different requirements, but down below I demonstrate how it can work for that. To summarize the problem: My requirement was that an optional argument "R" can accept an optional numeric value, and "R" is the only option that accepts an optional value of any kind. I wanted the "R" and its value to be able to be given in *any* format on the commandline, and be interpreted correctly. This wasn't working because depending on whether R's value immediately followed "R", and/or was separated by a space, and/or was followed immediately by other options or by a space and other options, the outcome for "R" and its value were unpredictable/unreliable. I'd like to have --long-opts too, but they're a frivolity I can do without at the moment. Making options that require arguments, require an "=" sign, was not agreeable to me. So, here's what I've got: Code:
while getopts ":l.#vhAabcdkpPswz1234567890" OPT ${ARGV}; do The feature of the program that is controlled by the "R" value (we'll call it the "R part"), is still present and still has its default setting of 1. But now, it retains its default setting unless any digits are given in the CLI arg-string. Digits given on the CLI are concatenated into a (string) integer number which is then evaluated after getopts is done. If the resulting integer number is just a "0", the "R part" of the program is disabled. If the integer number is >=2 then the "R part" is adjusted accordingly. I figure, if a person were to want to use this method for multiple "R" possibilities, it would be easy enough. Let's say it's like my situation, but you have "R" and "X", which can each take optional numeric arguments. Let's say the CLI receives: -abR2cdX5 During the getopts loop: 1) R is found, turning a flag on. The flag says "any upcoming digits will concat onto the R string". 2) 2 is found,so Rstring:=2 3) c is found, d is found.. X is found. If R flag is on, turn it off. So Rstring=2. Turn on X flag. 4) Now any upcoming digits concat onto X string. 5) etc.. etc... eventually Rstring=2 and Xstring=5 when done looping. So this is what I'm going to use I think. It's really simple to parse the input, and reliable. Feedback welcome! And meanwhile, thank you to everyone who has contributed here, and who adds anything further too. :) |
So just thought I would throw in some formats I like to use:
Code:
[0-9]) Ropt="${Ropt}${OPT}" ;; |
Hmm, yes I like the (( .. )) for integer tests, provided it isn't bash dependant. However, this here:
Code:
Ropt+=$OPT |
Actually it can be quite a tricky one:
Code:
Ropt+=$OPT # Concatenate strings |
Quote:
Quote:
Dash, I know nothing about, so can't comment. By now, you probably know most of this, but hey, a tutorial never hurts! http://bashcurescancer.com/the-60-se...-tutorial.html http://www.bash-hackers.org/wiki/dok...topts_tutorial http://www.shelldorado.com/goodcoding/cmdargs.html http://aplawrence.com/Unix/getopts.html I think one of those suggests that getopts doesn't work with long options. |
Thanks salasi, yes, more tutorials are never a bad idea, and I will look at those links you gave. One I recognize already: bashcurescancer - I have been there before (and will be there again!). The others I don't yet recognize but will be looking at.
The Dash/Bash difference would only be relevant if the built-in getopts was different between the two. And at this time, I'm not sure if they are or not. In the interest of portability, I would not want to depend on Dash any more than I like to write Bash-dependant code. I did read here and there that /bin/getopt is inferior to getopts, and that's unfortunate, as that's something that would be probably more poratble to use in code than a shell built-in. Anyhow, I have both Bash and Dash so I can test on both. Who knows, this program I'm writing is not extremely useful or widely interesting, so in the end it may only have to work right for me. You guys'll probably laugh if you know what this program (merely) does. :) But it keeps be occupied. As for long opts, I think that if I wanted to use them (as well as short opts), I would just not use getopts at all, I think I'd just do it the old fashioned way. I have no problem with that, but sometimes it's funny, like this time: the program I'm working on is pretty small and relatively non-complicated, so I find it peculiar spending (for example) 50-100 lines of code parsing options for a program that totals 125 lines.:D And when that 50-100 lines remains the same,. or gets longer by using getopts, well the end doesn't justify the means. :/ Thanks for the input salasi. |
All times are GMT -5. The time now is 11:37 PM. |