unrecognized option error: getopt command in shell - bash

I'm new to shell and Linux, it would be great if someone can help me find what is wrong in the command:
if ! options=$(getopt -n myscript -l a:,b:,cc:,dd:,ee:,ff:,gg:,hh: -- "$#"); then exit 1; fi
I get an error msg:
mhagent: unrecognized option '--hh'
options=' --aa '\''val1'\'' --ibb '\''val2'\'' --cc '\''val4'\'' --dd '\''val4'\'' --ee '\''val5'\'' --ff '\''val6'\'' --gg '\''val7'\'' --'
If I remove the last option: hh, it works fine.
if ! options=$(getopt -n myscript -l a:,b:,cc:,dd:,ee:,ff:,gg: -- "$#"); then exit 1; fi

Disclaimer: this answer assumes you are using getopt from util-linux.
OK, this is not at all obvious, but you have to specify an optstring (IE a list of short options you want to accept). Assuming you don't want to accept any short options, just pass an empty string.
Here's the synopsis:
getopt optstring parameters
getopt [options] [--] optstring parameters
getopt [options] -o|--options optstring [options] [--] parameters
Note that optstring is required in all 3 forms.
Since you need to pass -l, you have to use one of the ones with options, so your call to getopt should be either:
getopt -n myscript -l a:,b:,cc:,dd:,ee:,ff:,gg:,hh: -- '' "$#"
or:
getopt -n myscript -l a:,b:,cc:,dd:,ee:,ff:,gg:,hh: -o '' -- "$#"

Related

How to remove a single command from bash autocomplete

How do I remove a single "command" from Bash's auto complete command suggestions? I'm asking about the very first argument, the command, in auto complete, not asking "How to disable bash autocomplete for the arguments of a specific command"
For example, if I have the command ls and the system path also finds ls_not_the_one_I_want_ever, and I type ls and then press tab, I want a way to have removed ls_not_the_one_I_want_ever from every being a viable option.
I think this might be related to the compgen -c list, as this seems to be the list of commands available.
Background: WSL on Windows is putting all the .dll files on my path, in addition to the .exe files that should be there, and so I have many dlls I would like to remove in my bash environment, but I'm unsure how to proceed.
Bash 5.0's complete command added a new -I option for this.
According to man bash —
complete -pr [-DEI] [name ...]
[...] The -I option indicates that other supplied options and actions should apply to completion on the initial non-assignment word on the line, or after a command delimiter such as ; or |, which is usually command name completion. [...]
Example:
function _comp_commands()
{
local cur=$2
if [[ $cur == ls* ]]; then
COMPREPLY=( $(compgen -c "$cur" | grep -v ls_not_wanted) )
fi
}
complete -o bashdefault -I -F _comp_commands
Using #pynexj's answer, I came up with the following example that seems to work well enough:
if [ "${BASH_VERSINFO[0]}" -ge "5" ]; then
function _custom_initial_word_complete()
{
if [ "${2-}" != "" ]; then
if [ "${2::2}" == "ls" ]; then
COMPREPLY=($(compgen -c "${2}" | \grep -v ls_not_the_one_I_want_ever))
else
COMPREPLY=($(compgen -c "${2}"))
fi
fi
}
complete -I -F _custom_initial_word_complete
fi

Why does my bash script think an argument is not present?

I am trying to write a simple bash script that would execute as follows
$ ./export.sh -n <my-file-name> -a <my-api-key>
I am using this as a way to pass some arguments at build time in a Go project.
A very simple version of the script is:
#!/bin/bash
while getopts n:a option
do
case "${option}"
in
n) FILENAME=${OPTARG};;
a) APIKEY=${OPTARG};;
esac
done
if [ -z "$FILENAME" ]
then
FILENAME=downloader
fi
if [ -z "$APIKEY" ]
then
echo "[ERROR] Missing API key"
exit 1
fi
cd src && go build -o ../build/${FILENAME}.exe downloader -ldflags "-X api.APIServiceKey="${APIKEY}
If the FILENAME does not exist I provide a default value, however if APIKEY is missing I would like to exist and show a message.
Running the script with all arguments however throws the error as if APIKEY was missing.
You are missing a colon in the getopts call. Since you expect an argument to -a, there must be a colon after it in the optstring: while getopts n:a: option
Quoting the getopts man page:
When the option requires an option-argument, the getopts utility shall
place it in the shell variable OPTARG. [...] If a character is followed
by a <colon>, the option shall be expected to have an argument which
should be supplied as a separate argument.

Getopt parsing error in bash script (option declaration mistake)

I need to get 4 options (each with a short and a long version) in a bash script.
Here is what I did:
OPTS=`getopt -l :author,icon,channel,message: -o :aicm: -- "$#"` ||
exit 1
eval set -- "$OPTS"
while true; do
case "$1" in
-a|--author) echo "A:'$2'"; shift;;
-i|--icon) echo "I:'$2'"; shift 2;;
-m|--message) echo "M:'$2'"; shift 2;;
-c|--channel) echo "C:'$2'"; shift 2;;
--) shift; break;;
*) echo Error; exit 1;;
esac
done
And here is what I get:
command
docker run --rm -e SLACK_TOKEN slacker notify --channel foo
output
C:'--'
Error
Of course, I would like to have this output:
C:'foo'
Your getopt command looks a little funky. You seem to be using : as some sort of delimiter, here:
-l :author,icon,channel,message:
And here:
-o :aicm:
That doesn't make any sense. The : has special meaning in the options definitions; take a look at the getopt(1) man page:
-l, --longoptions longopts
The long (multi-character) options to be recognized. More than one
option name may be specified at once, by separating the names
with commas. This option may be given more than once, the longopts
are cumulative. Each long option name in longopts may be followed
by one colon to indicate it has a required argument, and by two colons
to indicate it has an optional argument.
The same is true of short options.
So assuming that all of your options take arguments, you would write:
OPTS=`getopt -l author:,icon:,channel:,message: -o a:i:c:m: -- "$#"` ||

Portable way to build up arguments for a utility in shell?

I'm writing a shell script that's meant to run on a range of machines. Some of these machines have bash 2 or bash 3. Some are running BusyBox 1.18.4 where bin/bash exists but
/bin/bash --version doesn't return anything at all
foo=( "hello" "world" ) complains about a syntax error near the unexpected "(" both with and without the extra spaces just inside the parens ... so arrays seem either limited or missing
There are also more modern or more fully featured Linux and bash versions.
What is the most portable way for a bash script to build up arguments at run time for calling some utility like find? I can build up a string but feel that arrays would be a better choice. Except there's that second bullet point above...
Let's say my script is foo and you call it like so: foo -o 1 .jpg .png
Here's some pseudo-code
#!/bin/bash
# handle option -o here
shift $(expr $OPTIND - 1)
# build up parameters for find here
parameters=(my-diretory -type f -maxdepth 2)
if [ -n "$1" ]; then
parameters+=-iname '*$1' -print
shift
fi
while [ $# -gt 0 ]; do
parameters+=-o -iname '*$1' -print
shift
done
find <new positional parameters here> | some-while-loop
If you need to use mostly-POSIX sh, such as would be available in busybox ash-named-bash, you can build up positional parameters directly with set
$ set -- hello
$ set -- "$#" world
$ printf '%s\n' "$#"
hello
world
For a more apt example:
$ set -- /etc -name '*b*'
$ set -- "$#" -type l -exec readlink {} +
$ find "$#"
/proc/mounts
While your question involves more than just Bash, you may benefit from reading the Wooledge Bash FAQ on the subject:
http://mywiki.wooledge.org/BashFAQ/050
It mentions the use of "set --" for older shells, but also gives a lot of background information. When building a list of argument, it's easy to create a system that works in simple cases but fails when the data has special characters, so reading up on the subject is probably worthwhile.

Bash: getopts passes a flag as wrong argument

I am trying to run a command that has multiple arguments. The command syntax is like so:
./foo -d directory -f file -v version app1 app2 app3 (this situation works)
However if I put the -v version after app1,2,3 it is passed as an argument and not the -v flag. How do I get the -v flag to work in either position with multiple arguments?
while getopts ":d:f:v:" OPTION
do
case $OPTION in
d ) IFS=","; directory=$OPTARG;;
f ) file=$OPTARG;;
v ) version=$OPTARG;;
* ) echo && usage;;
/? ) echo && usage;;
esac
done
shift $(( OPTIND - 1 ))
for dir in ${dirList} do
for f in ${file} ; do
echo $dir/$file
done
done
applications=$#
The standard option processing in Unix stops at the first non-option (excluding arguments for options marked with :). This is somewhat important, because otherwise you could not handle any files or directories starting with -.
If you really must break these rules, you can try the external program getopt(1), which uses a library function getopt(3). Reading the manual page for that function there are some remarks regarding argument shuffling. These may help you.
You could define it using getopt so that you do something like:
./foo -d directory -f file -v version -v app1 -v app2 -v app3

Resources