0

"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.

1
  • The terminology you're using here is nonstandard. Applying the terminology in the linked answer, "-a" and "sub_folder" are the arguments, and $1 and $2 are the parameters that receive them. (Note that shell functions don't have explicit signatures; they sort of have implicit signatures of $1, $2, $3, ...) The standard terms for arguments like -l are "flag" and "option". Jul 2, 2021 at 16:06

2 Answers 2

1

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
0

I found out about getops to properly split up all my parameters and this is what I ended up with:

my_ls () {
  /bin/ls -p --color=always --group-directories-first "$@"

  local OPTIND option
  while getopts ":l" option; do
    if [[ "$option" == "l" ]]; then
      count_info
    fi
  done
  shift $((OPTIND-1))
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.