Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining subcommands that take arguments in bash

Tags:

linux

bash

So i want to make a "program" that facilitates commands like the yum commands and anothers... when the program is finished i want put it in /usr/bin with the name "dafs"

i tested with this example which the filename is dafs

#!/bin/bash

$1 $2 $3

function yum {
    function maintenance {
        yum -y update
        yum -y upgrade
        yum clean all
    }

    function download {
        yum -y install --downloadonly $3
    }

}

but when i run ./dafs yum maintenance or ./dafs yum download http it don't work i guess because the syntax is incorrect..

So, how can i pass arguments to functions or sub functions, like the example above?

like image 505
DiogoSaraiva Avatar asked May 16 '16 15:05

DiogoSaraiva


People also ask

Can bash functions take arguments?

Instead, Bash functions work like shell commands and expect arguments to be passed to them in the same way one might pass an option to a shell command (e.g. ls -l ). In effect, function arguments in Bash are treated as positional parameters ( $1, $2.. $9, ${10}, ${11} , and so on).

How do we pass arguments to function in bash?

To pass any number of arguments to the bash function simply put them right after the function's name, separated by a space. It is a good practice to double-quote the arguments to avoid the misparsing of an argument with spaces in it. The passed parameters are $1 , $2 , $3 …

Can shell script take arguments?

Arguments can be passed to the script when it is executed, by writing them as a space-delimited list following the script file name.


1 Answers

A best-practice way to define subcommands is with a prefixed namespace, and a "launcher" function. This is how git does it, for instance (using git-foo and git-bar commands for git foo and git bar).

Here, I'm using double-underscores rather than a single dash as the separator, as underscores (unlike dashes) are defined as valid within function names by the POSIX sh standard.

yum__maintenance() {
  command yum -y update
  command yum -y upgrade
  command yum clean all
}

yum__download() {
  command yum -y install --downloadonly "$@"
}

yum() {
  local cmdname=$1; shift
  if type "yum__$cmdname" >/dev/null 2>&1; then
    "yum__$cmdname" "$@"
  else
    command yum "$cmdname" "$@" # call the **real** yum command
  fi
}

# if the functions above are sourced into an interactive interpreter, the user can
# just call "yum download" or "yum maintenance" with no further code needed.

# if invoked as a script rather than sourced, call function named on argv via the below;
# note that this must be the first operation other than a function definition
# for $_ to successfully distinguish between sourcing and invocation:
[[ $_ != $0 ]] && return

# make sure we actually *did* get passed a valid function name
if declare -f "$1" >/dev/null 2>&1; then
  # invoke that function, passing arguments through
  "$@" # same as "$1" "$2" "$3" ... for full argument list
else
  echo "Function $1 not recognized" >&2
  exit 1
fi

Items of note:

  • "$@" expands to the full list of arguments passed to the current item in scope, preserving argument boundaries and avoiding glob expansion (unlike $* and unquoted $@).
  • shift pops the first argument ($1) off the front of the list, leaving the new value of "$@" one shorter than the old list.
  • The command builtin causes the real yum command to be called, rather than simply recursing into the yum function again, when no subcommand exists.
  • declare -f funcname returns true (and prints that function's definition) if really passed a function. type, by contrast, returns true if passed any kind of runnable command. Thus, using type "yum__$cmdname" allows yum__foo to be defined as an external script or any other type of command, not just a function, whereas the declare -f "$1" done later allows only functions to be run.

A final thing to consider, if you don't intend to support being sourced, would be leaving out the yum function, but expanding your launcher to recognize subcommands itself:

if declare -f "${1}__$2" >/dev/null; then
  func="${1}__$2"
  shift; shift    # pop $1 and $2 off the argument list
  "$func" "$@"    # invoke our named function w/ all remaining arguments
elif declare -f "$1" >/dev/null 2>&1; then
  "$@"
else
  echo "Neither function $1 nor subcommand ${1}__$2 recognized" >&2
  exit 1
fi

In this case, a subcommand named by the first two arguments is always searched for, followed by a function named by the first argument only.

like image 142
Charles Duffy Avatar answered Oct 25 '22 23:10

Charles Duffy