3

After installing microk8s (Micro Kubernetes) on my local machine, one of the commands I encountered was microk8s.enable dns which can also be run as microk8s enable dns. This doesn't seem to be a universal thing. git status is a valid command but git.status is not. How do Linux systems support such type of command structures? How can I incorporate this behavior in my Bash scripts?

3
  • Command names are as arbitrary as filenames, because that is what they most often are. – Kusalananda May 9 '20 at 17:40
  • What do you mean by "How can I incorporate this behavior in my Bash scripts?" Do you mean "How can I write a script that works this way?" – Kusalananda May 9 '20 at 18:38
  • @Kusalananda Yes, I meant "How can I write scripts that work this way?" – Yash Jakhotiya May 11 '20 at 10:45
4

You'll sometimes see programs (and scripts) inspect the name of the file that was used to invoke the program and condition behavior off of that.

Consider for example this file and symbol link:

$ ls -l
-rwxr-xr-x  ... foo
lrwxr-xr-x  ... foo.bar -> foo

And the content of the script foo:

#!/bin/bash

readonly command="$(basename "${0}")"
subcommand="$(echo "${command}" | cut -s -d. -f2)"

if [[ "${subcommand}" == "" ]]; then
    subcommand="${1}"
fi

if [[ "${subcommand}" == "" ]]; then
    echo "Error: subcommand not specified" 1>&2
    exit 1
fi

echo "Running ${subcommand}"

The script parses the command name looking for a subcommand (based on the dot notation in your question). With that, I can run ./foo.bar and get the same behavior as running ./foo bar:

$ ./foo.bar
Running bar

$ ./foo bar
Running bar

To be clear, I don't know that that's what microk8s.enable is doing. You could do a ls -li $(which microk8s.enable) $(which microk8s) and compare the files. Is one a link to the other? If not, do they have the same inode number?

3
  • you can use prefix-removing expansion in the shell to avoid calling basename and cut: readonly command=${0##*/} and subcommand="${command##*.}" – ilkkachu May 9 '20 at 21:20
  • @ilkkachu yes, thank you, but IMO that's far less readable to a novice. – Andy Dalton May 9 '20 at 23:04
  • This seems like a legit way to do this. Thanks for your answer. Although, I would like to add the following - i. which revealed microk8s and microk8s.enable are two different files with different inode numbers in /snap/bin directory. ii. One is not a symbolic link to the other but both are symbolic links to /usr/bin/snap. This is due to the way snap installs applications. How does microk8s actually incorporate this is not so easy to find out it seems. – Yash Jakhotiya May 11 '20 at 10:50
2

This has become a fairly common way of providing a tool that can perform multiple actions depending on what "sub command" is being used. It is not standardized in any way that I know of and using a dot as a separator between the base command name and the sub command when writing them together is definitely not universal for these tools.

Some tools can only be called with a sub command, like git (git by itself provides a help text if it is called alone), but provides manuals for the sub commands like man command-subcommand (as is the case for the git sub commands).

You have obviously found a tool that may be called as command-subcommand (but with a dot) or as command subcommand. In this case, you will likely find that both the base command and each combined command are either symbolic links or hard links to one and the same file.

A program (whether a script or a compiled binary) can easily inspect by what name it has been invoked, and with what arguments, and adjust its actions accordingly.

Below is an example of a fictional process command that can take a sub command as the first argument, as in process action, or be called with the sub command like process-action.

The sub commands implemented by this script is compile, debug, and mogrify.

#!/bin/sh

basecmd=process         # base command name
cmd=${0##*/}            # get command name ( basename "$0" )
subcmd=                 # no sub command yet

# Now pick out the sub command from the command name,
# or from the first argument.  Then fail if unsuccessful.

case $cmd in
        "$basecmd"-*)   # has sub command in command name
                subcmd=${cmd#$basecmd-}
esac

if [ -z "$subcmd" ] && [ "$#" -ge 1 ]; then
        # called as "process action"
        # rather than as "process-action"
        subcmd=$1
        shift   # remove sub command from argument list
fi

if [ -z "$subcmd" ]; then
        echo 'No action' >&2
        exit 1
fi

# Act on the sub command.
# Each action would probably be implemented as a function,
# possibly called as
#     somefunction "$@"
# ... passing the remaining command line argument to it.

case $subcmd in
        compile)        # do "compile" action
                echo 'compile'
                ;;
        debug)          # do "debug" action
                echo 'debug'
                ;;
        mogrify)        # do "mogrify action"
                echo 'mogrify'
                ;;
        *)
                printf 'Invalid action "%s"\n' "$subcmd" >&2
                exit 1
esac

I've written this for POSIX sh, as there is nothing mysterious that require bash for this to work. A C program would do things in a similar manner, as would a program written in any other compiled or interpreted language. This also does not require Linux; I'm writing and testing this on OpenBSD and it should work on any POSIX system.

Along with this base process script would be a set of hard or symbolic links, one for each sub command. Here, I've chosen to create hard links:

$ ls -li
total 8
244420 -rwxr-xr-x  4 kk  wheel  538 May  9 21:55 process
244420 -rwxr-xr-x  4 kk  wheel  538 May  9 21:55 process-compile
244420 -rwxr-xr-x  4 kk  wheel  538 May  9 21:55 process-debug
244420 -rwxr-xr-x  4 kk  wheel  538 May  9 21:55 process-mogrify

Each of these names is just another name for the same script.

Test-running:

$ ./process mogrify
mogrify
$ ./process-mogrify
mogrify
$ ./process
No action
$ ./process-compile
compile
$ ./process compile
compile
$ ./process compilee
Invalid action "compilee"
2
  • Thanks for your answer. They indeed turned out to be different filenames. But the symlinks are actually to /usr/bin/snap (as are all snap installed executables). What you have provided is a good enough way to incorporate this behavior, but how does microk8s do this is not so easy to find out. – Yash Jakhotiya May 11 '20 at 10:48
  • 1
    @YashJakhotiya If the microk8s tools are scripts, you should be able to investigate them easily enough, otherwise you would have to hunt down the source code for the tools, if the source is available publicly at all. I have no experience with these tools. – Kusalananda May 11 '20 at 11:05

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.