Debian's update-alternatives
system traditionally operates at the system level, affecting all users. This becomes problematic when:
- Developers need different versions of tools (e.g., Python 2.7 vs 3.x)
- System administrators want to restrict changes to root
- Team environments require personalized toolchains
The alternatives mechanism maintains symbolic links in /etc/alternatives
that point to actual executables. A typical system-wide change looks like:
sudo update-alternatives --config python
Method 1: PATH Manipulation
Create a ~/.local/alternatives
directory and prepend it to your PATH:
mkdir -p ~/.local/alternatives
echo 'export PATH="$HOME/.local/alternatives:$PATH"' >> ~/.bashrc
Then create manual symlinks:
ln -s /usr/bin/python3.8 ~/.local/alternatives/python
Method 2: Environment Modules
Install and configure environment-modules
:
sudo apt install environment-modules
mkdir -p ~/privatemodules
echo "prepend-path PATH /path/to/your/preferred/version" > ~/privatemodules/python
Method 3: Virtual Environments
For language-specific alternatives, virtual environments work well:
python -m venv ~/venvs/myproject
source ~/venvs/myproject/bin/activate
Create user-specific wrapper scripts in ~/bin
:
#!/bin/bash
# ~/bin/python
exec /usr/bin/python3.9 "$@"
Make executable:
chmod +x ~/bin/python
When implementing user-specific alternatives:
- Avoid giving users write access to system directories
- Consider SELinux/AppArmor policies if applicable
- Document custom paths for team environments
Check which version is actually being executed:
which python
type -a python
readlink -f $(which python)
The standard update-alternatives
tool in Debian-based systems operates at a system-wide level. When you run commands like:
sudo update-alternatives --config editor
This change affects all users on the system. In multi-user environments or development workstations, this can cause conflicts when different users prefer different tools.
While Debian doesn't provide a built-in per-user alternative system, we can implement a workaround using environment variables and wrapper scripts:
- Create a directory for user alternatives:
- Add it to your PATH in ~/.bashrc:
- Create wrapper scripts for each alternative:
mkdir -p ~/.alternatives/bin
export PATH="$HOME/.alternatives/bin:$PATH"
#!/bin/bash
# ~/.alternatives/bin/editor
if [ -f ~/.alternatives/editor ]; then
exec $(cat ~/.alternatives/editor) "$@"
else
exec /usr/bin/nano "$@"
fi
Create a user-level alternative manager script:
#!/bin/bash
# ~/bin/user-alternatives
ALTERNATIVE_NAME=$1
ALTERNATIVE_PATH=$2
mkdir -p ~/.alternatives
echo "$ALTERNATIVE_PATH" > ~/.alternatives/$ALTERNATIVE_NAME
chmod +x ~/.alternatives/bin/$ALTERNATIVE_NAME
Usage example:
user-alternatives editor /usr/bin/vim
user-alternatives x-terminal-emulator /usr/bin/kitty
For better compatibility, we can make our user alternatives fall back to system alternatives:
#!/bin/bash
# ~/.alternatives/bin/x-terminal-emulator
if [ -f ~/.alternatives/x-terminal-emulator ]; then
exec $(cat ~/.alternatives/x-terminal-emulator) "$@"
else
exec $(update-alternatives --query x-terminal-emulator | grep '^Best:' | cut -d' ' -f2-) "$@"
fi
Create a configuration file for easier management (~/.alternatives.conf):
# User alternatives configuration
editor=/usr/bin/vim
x-terminal-emulator=/usr/bin/kitty
browser=/usr/bin/firefox
Then modify the user-alternatives script to read from this config:
#!/bin/bash
while read -r line; do
[[ "$line" =~ ^# ]] && continue
name=$(echo "$line" | cut -d'=' -f1)
path=$(echo "$line" | cut -d'=' -f2)
echo "$path" > ~/.alternatives/$name
done < ~/.alternatives.conf