How to check in bash if a function's arguments does not contain any parameters
"Argument" vs. "Paramter"
First of all, let me clarify what I mean by:
if a function's arguments does not contain any parameters
Given the following ls
command:
$ ls -a sub_folder
I call -a sub_folder
the function's arguments and only -a
I call parameter (as explained here).
Question
Now, in my bash
script, how can I check if a function's arguments does not contain any parameters?
I thought of checking that no argument in $@
starts with a -
. But how could I do that?
I'm new to bash and therefore not experienced enough but in TypeScript I would do something like:
if (args.some(arg => arg.startsWith("-"))) {
# parameters found
} else {
# no parameters
}
What I want to do
I actually wanted to write my own ls
function to also displays how many folders and files there are (via count_info
). But this should only be displayed if I call my_ls
with a -l
parameter (in my case, all my aliases except l
use the -l
option).
In my bashrc
I have the following code:
alias l="my_ls" # the only call with no parameters
alias ll="l -l"
alias la="l -l --almost-all"
alias ls="l -lAsSh"
my_ls () {
/bin/ls -p --color=always --group-directories-first "$@"
if [ ! -z "$1" ]; then # check if there were parameters
count_info
fi
}
This works for all the normal alias calls l
, ll
, etc.
But as soon as I to something like l subfolder
my function does not work properly anymore as my check [ ! -z "$1" ]
fails.
Another solution would be to simply test if there is a -l
parameter but how can I do that? Especially with also having parameter combinations like -lAsSh
in mind.
You cannot check this in a completely generic way, because even two programs using the same type of option parser will still have different expectations. (And of course not all programs use the same syntax at all.)
For example, --color=always
is obviously a parameter that takes a value (in the getopt_long syntax). But how about --color always
? One program might consume "always" as the value of the parameter, but another program might use "--color" as a boolean parameter and treat "always" as a standalone positional argument. Both are valid ways to specify the same thing.
(In ls, "--color" takes an optional value, so --color always
will leave the "always" argument as a file name to list… but "--format" takes a required value, and if you use --format long
it will consume the argument as an option value.)
So your check needs to exactly duplicate what ls
itself does – you'll need to have a list of all parameters (short and long) which require a value (but not those where the value is optional), and if you see an option which you know takes a required value you'll know that the following item should be ignored.
(Similarly, getopt_long allows --name
to be shortened to --nam
or even --na
, so your code needs to account for that as well. It also needs to know that a lone --
stops parameter processing, and even arguments that begin with a dash are no longer treated as parameters beyond that point.)
In fact, it would be easiest for you to actually use an option parsing library that claims to be compatible with the getopt_long() function from Glibc. (But for example, Python's argparse wouldn't work as it handles optional values differently than ls would.)
This is actually easier within bash, by calling the /bin/getopt
tool (not to be confused with the getopts
bash built-in!). It will actually use getopt_long() and will sort all arguments so that you first have options neatly split out, then a --
, and finally non-option arguments.
- Input:
-abT8 one --color two --format three --size four
- Output:
-a -b -T 8 --color "" --format three --size -- one two four
With this, all you need to check is whether there is anything in the output that follows the --
argument. For example:
#!/bin/bash
short="abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UXZ1"
long="all,almost-all,author,escape,block-size:,ignore-backups,color::,\
directory,dired,classify,file-type,format:,full-time,group-directories\
-first,no-group,human-readable,si,dereference-command-line,dereference\
-command-line-symlink-to-dir,hide:,hyperlink::,indicator-style:,inode,\
ignore:,kibibytes,dereference,numeric-uid-gid,literal,indicator-style:\
,hide-control-chars,show-control-chars,quote-name,quoting-style:,rever\
se,recursive,size,sort:,time:,time-style:,tabsize:,width:,context,help\
,version"
parsed=$(getopt -o "$short" -l "$long" -- "$@") || exit
eval "args=($parsed)"
declare -p args # print the array's contents
end=0; found=0
for arg in "${args[@]}"; do
if (( end )); then
echo "found non-option arg '$arg'"
exit 1
elif [[ $arg == "--" ]]; then
end=1
fi
done
echo "did not find any non-option args"
exit 0