getopt finds no ambiguous optional argument - getopt

I wrote down a shell script which uses the getopt command. The list of long options supplied to the getopt command include the following three different options:
localaddress
localport
listen
When I run my script with
myscript.sh --local xxxx
which clearly contains an ambiguous option (--local), getopt returns it as '--local-address' and a zero code.
++ getopt -o a:p: --long localaddress:,localport: -- --local 172.30.2.4
+ AUX='-- --localaddress '\''172.30.2.4'\'
But if I try
myscript.sh --l xxxx
here getopt does find it ambiguous:
getopt: option `--l' is ambiguous
I think this behaviour is weird given that the manual pages of getopt(1) show:
Long options may be abbreviated, as long as the abbreviation is not ambiguous.
What have I missed?
Thanx in advance
P.D: tested in RHEL5
Addendum
After I read the answer of Jonathan Leffler, I tried some tests -- pay attention on the colon pattern:
# getopt -V
getopt (enhanced) 1.1.4
# getopt -o a:p: --long localaddress,localport,listen -- --l xxx
--localaddress -- 'xxx'
# getopt -o a:p: --long localaddress,localport,listen: -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
# getopt -o a:p: --long localaddress,localport:,listen -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
# getopt -o a:p: --long localaddress,localport:,listen: -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
# getopt -o a:p: --long localaddress:,localport,listen -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
# getopt -o a:p: --long localaddress:,localport,listen: -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
# getopt -o a:p: --long localaddress:,localport:,listen -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
# getopt -o a:p: --long localaddress:,localport:,listen: -- --l xxx
--localaddress 'xxx' --
UPDATE on 2014-05-26 -- testing getopt_long(3)
I created a simple C program to test the getopt_long(3) function.
In my source code, the array of the structure "option" passed to getopt_long(3) contains the definition of the three long options "xxaaa", "xxxyy" and "xxxzz" -- They all start with the same string "xx" to test the ability of getopt_long(3) to detect the ambiguous options.
/*
FIELD NAMES OF option STRUCTURE
name has_arg flag val
*/
{ "xxaaa", no_argument, NULL, 9},
{ "xxxyy", no_argument, NULL, 7},
{ "xxxyz", no_argument, NULL, 7},
{ "mmmAA", no_argument, NULL, 3},
{ "mmmBB", required_argument, NULL, 3},
(see the manual page of getopt_long(3) for details about the purposes of those fields).
Pay attention on that an ambiguous option as "--xx" has three possible candidates, but "---xxx" has only two ones.
The behaviour of getopt_long changes according to the values of two struct fields named "has_arg" and "val":
WHEN
the value of "val" field is the same in every possible candidate
AND
the value of the "has_arg" field is the same in every possible candidate,
THEN
the function getopt_long wrongly complains NOTHING about ambiguity
and the first candidate is returned
OTHERWISE
an error message about ambiguity is reported.
From the above example:
test 1. "--xxx" is WRONGLY admitted as "--xxxyy".
test 2. "--xx" is properly refused as ambiguous.
test 3. "--mmm" is properly refused as ambiguous.
The getopt(1) command assigns a 2 (labeled as LONG_OPT in the source code) in the "val" field of every "user-defined long option". In the case starting this thread, the array of "option" structures would look like:
{ "localaddress", required_argument, NULL, 2},
{ "localport", required_argument, NULL, 2},
Therefore the only way to getopt(1) detect an ambiguous long option is that that candidates have different values in their "has_arg" field.
QUESTIONS
Q1. Why does getopt_long(3) behave that way?
Q2. Why does getopt_long_only(3) not?
Q3. How can the creators of getopt_long(3) be notified? -- I'm not into the linux kernel
development nor I do not use to visit linux kernel websites.
WORKAROUND SUGGESTION
As I cannot answer Q1 yet, I think the source code of getopt(1) command might be modified to assign different values in the "val" field for each user-defined long option, instead of the same 2 (LONG_OPT) value. As getopt_long(3) can return single ASCII character in the case of short options, those values should fall out of ASCII map -- val >= 256

Very curious! I am at least nominally running the same version of getopt (admittedly not on RHEL5, which is fairly old, now, IIRC — not that age has much to do with it), but I can't reproduce the results you get. For every one of the operational examples in the addendum, I get the warning message getopt: option `--l' is ambiguous (and the exit status of getopt is 1, indicating failure).
Test script
for la in '' :
do
for lp in '' :
do
for li in '' :
do
(
set -x
getopt -o a:p: --long localaddress${la},localport${lp},listen${li} -- --l xxx
)
echo $?
done
done
done
Example output
+ getopt -o a:p: --long localaddress,localport,listen -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
+ getopt -o a:p: --long localaddress,localport,listen: -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
+ getopt -o a:p: --long localaddress,localport:,listen -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
+ getopt -o a:p: --long localaddress,localport:,listen: -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
+ getopt -o a:p: --long localaddress:,localport,listen -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
+ getopt -o a:p: --long localaddress:,localport,listen: -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
+ getopt -o a:p: --long localaddress:,localport:,listen -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
+ getopt -o a:p: --long localaddress:,localport:,listen: -- --l xxx
getopt: option `--l' is ambiguous
-- 'xxx'
1
This is self-consistent and consistent with the documentation. Can you run the script shown and verify the output?

Related

Make tree command print & instead of & (And other special characters) on JSON file

I've this example Tree structure
TreeTest/
├── something?.txt
└── something&.txt
I want to generate a JSON file of it to use it on another script, and i require the exact same names, since they are used as keys. I readed the man, and used this command tree -J -o tree.json TreeTest/, but it prints this
[
{"type":"directory","name":"TreeTest/","contents":[
{"type":"file","name":"something?.txt"},
{"type":"file","name":"something&.txt"}
]},
{"type":"report","directories":0,"files":2}
]
With & instead of just &. I tried using tree -J -N -o tree.json TreeTest/ and tree -J --charset utf8 -o tree.json TreeTest/, but was the same output. How can i make the output be the exact names, using & and any other special character that could have this problem?
Until this can be fixed and deployed everywhere:
tree . -J | recode html..utf8
But this fail with "piña colada" or "你好"
The perl alternative works with all:
tree . -J | perl -n -mHTML::Entities -e 'print HTML::Entities::decode_entities($_)'
Sample output:
[
{"type":"directory","name":".","contents":[
{"type":"file","name":"a"},
{"type":"file","name":"a&"},
{"type":"file","name":"<a>&c"},
{"type":"file","name":"b"},
{"type":"file","name":"c"},
{"type":"file","name":"d"},
{"type":"file","name":"e"},
{"type":"file","name":"f"},
{"type":"file","name":"filename.R10011.out"},
{"type":"file","name":"g"},
{"type":"file","name":"h"},
{"type":"file","name":"i"},
{"type":"file","name":"j"},
{"type":"file","name":"k"},
{"type":"file","name":"l"},
{"type":"file","name":"piña colada"},
{"type":"directory","name":"r","contents":[
]},
{"type":"file","name":"你好"}
]},
{"type":"report","directories":1,"files":17}
]

Differentiate between calling an unique argument and providing a string along with it using argparse in a mutual exclusive group

I have been checking and I cannot find this functionality.
This is an extract of the help functionality of the script I am writing, as an example:
usage: my_parser.py [-h] (-s SERIAL_NUMBER | -e EVENT)
And I want my script to accept, for example:
$ my_parser.py -s
$ my_parser.py -s 1234
$ my_parser.py -e
$ my_parser.py -e 9876
The logic is that if no argument is passed to -s or -e, then the software must return all entries. If an argument is passed, like -s 1234 or -e 9876 then only the entry with that ID must be returned.
The partial code looks like the following:
parser = ArgumentParser()
group_input = parser.add_mutually_exclusive_group(required=True)
group_input.add_argument("-s","--serial-number", default=1) # also, default=None
group_input.add_argument("-e","--event", default=1) # also, default=None
But then, when I run it, if no argument is provided, the software errors and exits. For example: $ my_parser.py -s
I also tried to add something like action='store_true' within the add_argument function, without success. Why? I assume that it doesn't expect any incoming arguments.
Anyway, even if we could make it work, how to differentiate it?
For example, if I could make the software continue running, let's imagine I introduce the following:
$ my_parser.py -s 1234
If we parse it, and save it to a variable a, then a.serial_number is 1234, but a.event is 1. I even didn't mention -e in the script and it already has a value. So that would be another problem to face.
I think you just need to add `nargs='?' to the arguments. This allows for a 3 way input - a default, a constant, and user value.
The fact that you are using these in a mutually exclusive group doesn't matter.
In [3]: parser = argparse.ArgumentParser()
...: group_input = parser.add_mutually_exclusive_group(required=True)
...: group_input.add_argument("-s","--serial-number", default=1,nargs='?',const=2,type=int);
...: group_input.add_argument("-e","--event", default=1,nargs='?',const=3,type=int);
...:
...:
In [4]: parser.parse_args('-s'.split())
Out[4]: Namespace(event=1, serial_number=2)
In [5]: parser.parse_args('-s 1234'.split())
Out[5]: Namespace(event=1, serial_number=1234)
In [6]: parser.parse_args('-e'.split())
Out[6]: Namespace(event=3, serial_number=1)
In [7]: parser.parse_args('-e 1232'.split())
Out[7]: Namespace(event=1232, serial_number=1)
In [8]: parser.parse_args('-e 1232 -s'.split())
usage: ipython3 [-h] (-s [SERIAL_NUMBER] | -e [EVENT])
ipython3: error: argument -s/--serial-number: not allowed with argument -e/--event
....
Because the group is required you have to provide one of -s or -e
In [9]: parser.parse_args(''.split())
usage: ipython3 [-h] (-s [SERIAL_NUMBER] | -e [EVENT])
ipython3: error: one of the arguments -s/--serial-number -e/--event is required
....
A store_true argument can also be used.

bash: command works in terminal, not in script

I am doing
a="$(openssl x509 -in /path/to/pemfile.pem -text -noout)";
echo ${a} |grep -a1 -b2 Signature
which works quite well if i put this line into terminal
However, if i put the very same line in a file executeme.sh, chmod +x executeme.sh, ./executeme.sh
I seems does not create the linebreaks in the variable, resulting grep to receive just one line. output is as follows on the terminal:
20- Version: 3 (0x2)
38- Serial Number: 32 (0x27)
64: Signature Algorithm: md5WithRSAEncryption
107- Issuer: C=EN, ST=a, L=b, O=c, OU=d, CN=e
244- Validity
------
[...]
The script outputs the entire certificate , as if i would only do a="$(openssl ...)"; echo ${a}
Do
echo "${a}" |grep -a1 -b2 Signature #mind the double quotes
Why doublequotes?
See [ this ] answer + [ this ] answer.

Why is grep not finding integer*2?

For example
grep -n 'integer*2' *.f
Shows nothing.But
grep -n '*2' *.f
main.f:57: integer*2 itime(nxmax)
main.f:605: dxy=((xsource(is)-xobs)**2+(ysource(is)-yobs)**2)**.5
main.f:622: chisum=chisum+diff2/uobs**2
model.f:15: integer*2 veli(nxmax)
model.f:52: size2=size**2
time.f:151: integer*2 itime(nxmax)
I really do not understand this.
* is an operator, meaning "match the previous term 0 or more times". So integer*2 matches
intege2
integer2
integerr2
integerrr2
:
none of which appear in your program. * at the beginning of an RE is meaningless (there's no previous term), so is either ignored or treated as match for a *. Escape the * to have it match an actual star:
'integer\*2'
Your grep is using a regex. (Star is being interpreted differently than you might believe). Try
grep -F -n 'integer*2' *.f
Because grep is interpreting the search argument as a regular expression, in which * is meant as "zero or more of the preceding". So 'integer*2 would match intege2 as well as integerrrrr2 since * applies to the preceding r but will not match the literal integer*2.
Escape it with a backslash to interpret it as a literal * and you should get the desired matches:
grep -n 'integer\*2' *.f

using grep command to search man pape

Although the man page have so many information, I need a little bit of it at once. For example, objdump -f kernel.o, but I forgot the feature of the flag '-f'.
I try this but fail.
man objdump | grep -e '*.(\-)f.*'
The error message as following:
<standard input>:161: warning [p 1, 5.5i]: can't break line
<standard input>:594: warning [p 6, 6.5i, div `an-div', 0.0i]: can't break line
How do I search the man page using grep?
$ man objdump | grep -e "-f"
<standard input>:161: warning [p 1, 5.5i]: can't break line
<standard input>:590: warning [p 6, 6.5i, div `an-div', 0.0i]: can't break line
[-f|--file-headers]
[-F|--file-offsets]
[--file-start-context]
[-s|--full-contents]
-a,-d,-D,-e,-f,-g,-G,-h,-H,-p,-P,-r,-R,-s,-S,-t,-T,-V,-x must be given.
-f
--file-headers
--file-offsets
--file-start-context
--full-contents
all of -a -f -h -p -r -t.
Is that what you want?

Resources