0

It sometimes happens that there are multiple external commands with the same file name, and the only way to choose which one to call is to use its full path, which is impractical. My idea is that they would be sorted into namespaces like it is common in programming languages, although I don't thing that it is possible with usual tools.

My idea is this: Consider that I have clang installed, and I would install WASI SDK which has its own clang. So I would use it this way:

wasi-sdk.clang program.c -o program.wasm

Or I could bring the namespace wasi-sdk into the main namespace like this:

use wasi-sdk
clang program.c -o program.wasm

The closest what I can do with usual tools is that I would put one clang to /usr/bin/ and the other to /usr/bin/wasi-sdk/ and call the other as wasi-sdk/clang. But that doesn't work because wasi-sdk/clang would be treated as a path relative to the current directory.

Is there a way to make it work with usual tools? Or is there a shell which supports namespaces like how I imagine them?

matj1
  • 199

1 Answers1

0

First, create a suitable directory structure:

# your desired namespace
mkdir -p -- "$HOME"/.namespaces/wasi-sdk
# some testing namespaces
mkdir -p -- "$HOME"/.namespaces/bar "$HOME"/.namespaces/baz "$HOME"/.namespaces/surprise

Populate the directories:

# your desired utility
ln -s -- /usr/bin/wasi-sdk/clang "$HOME"/.namespaces/wasi-sdk/clang
# some testing utilities
ln -s -- /usr/bin/date "$HOME"/.namespaces/bar/foo
ln -s -- /usr/bin/echo "$HOME"/.namespaces/baz/foo
ln -s -- /usr/bin/date "$HOME"/.namespaces/surprise/ls
ln -s -- /usr/bin/ls   "$HOME"/.namespaces/surprise/date
ln -s -- /usr/bin/ls   "$HOME"/.namespaces/surprise/foo

Create more symlinks:

( cd -- "$HOME"/.namespaces \
&& find . ! -type d -path './*/*' -exec sh -c '
   for f do
      n="$(printf "%s\\n" "$f" | sed "s|/|.|g; s|^\\.\\.|./|")"
      ln -s -- "$f" "$n"
   done
' find-sh {} +
)

Do not worry if you don't understand the above code; in the future you may as well create and manage symlinks manually. This is how the directory structure looks now:

$ ( cd -- "$HOME"/.namespaces && tree )
.
├── bar
│   └── foo -> /usr/bin/date
├── bar.foo -> ./bar/foo
├── baz
│   └── foo -> /usr/bin/echo
├── baz.foo -> ./baz/foo
├── surprise
│   ├── date -> /usr/bin/ls
│   ├── foo -> /usr/bin/ls
│   └── ls -> /usr/bin/date
├── surprise.date -> ./surprise/date
├── surprise.foo -> ./surprise/foo
├── surprise.ls -> ./surprise/ls
├── wasi-sdk
│   └── clang -> /usr/bin/wasi-sdk/clang
└── wasi-sdk.clang -> ./wasi-sdk/clang

5 directories, 12 files

So in the "namespace" directories there are symlinks to real utilities. Additionally in the "main" directory there are symlinks (named namespace.name) to those symlinks.

Add the "main" directory to your $PATH:

PATH="$HOME/.namespaces:$PATH"

Now bar.foo is date and baz.foo is echo (try baz.foo Hello World). More importantly wasi-sdk.clang should work for you.

To be able to use use wasi-sdk as you wish, define a function that will manipulate your $PATH:

use () {
   PATH="$(
      dir="$HOME"/.namespaces
      oldpath="$PATH"
      newpath="$dir"
      for namespace do
         entry="$dir/$namespace"
         if [ -d "$entry" ]; then
            newpath="$newpath:$entry"
         else
            printf 'Namespace %s not found.\n' "$namespace" >&2
         fi
      done
      while [ -n "$oldpath" ]; do
         IFS=: read -r entry oldpath <<EOF
$oldpath
EOF
         case "$entry" in
            "$dir"/* | "$dir")
               entry=''
               ;;
            *)
               entry=":$entry"
               ;;
         esac
         newpath="$newpath$entry"
      done
   printf '%s' "$newpath"
   )"
}

Now you can run use wasi-sdk and it will do what you want. You can run use surprise to "swap" date and ls (note this ls will fail if there is an alias like alias ls='ls --color=auto'), and to make foo behave like ls. Better, you can run use bar surprise to "swap" date and ls (like in surprise), and to make foo behave like date (like in bar, overriding surprise).

In general use namespace1 namespace2 namespace3 ... allows you to use multiple namespaces (namespace1 has the highest priority). The function is quite dumb (e.g. it will not deduplicate the list, it will happily accept ../../..).

To stop using namespaces this way, run use without arguments. Note it will still add the "main" directory to the path, even if it wasn't there before. This is deliberate, so you can choose not to change your $PATH account-wide and "activate" the support for this namespace.name syntax only on demand by invoking use (with or without arguments) for the first time.

Note that in general a program may adjust its behavior according to the name used to call it (example). Running such a program via a symlink named namespace.name may break things.