Add a Login Shell Safely with add-shell
Someone installed a new shell and wants it offered as a login option, and the only thing standing in the way is one text file, /etc/shells. add-shell edits that list for you and gives you a clean way back out. The installed command is from Debianutils 5.17build1 on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about ten minutes. You need a shell account with sudo access, the exact path of a shell that is already installed, and a maintenance window if the file is used by an authentication service. This changes a system authentication list, so read the proposed path before running the privileged command.
1. Check the installed command and its contract
Confirm which executable will run and record its package version. These are ordinary read-only commands:
$ command -v add-shell
/usr/sbin/add-shell
$ dpkg-query -W -f='${Package} ${Version}\n' debianutils
debianutils 5.17build1
$ man -P cat 8 add-shell | sed -n '1,35p'
The command accepts one or more shell names, in the documented form add-shell shellname [shellname...]. It copies /etc/shells to /etc/shells.tmp, adds entries that are not already present, then copies the temporary file back. A successful run normally prints nothing.
Checkpoint
You have confirmed the binary and package version. The command does not install a shell, make a file executable, or change a user's login shell. It only edits the list used to identify valid login shells.
2. Inspect the current list and the proposed shell
Read the current file before changing it:
$ sed -n '1,120p' /etc/shells
# /etc/shells: valid login shells
/bin/sh
/usr/bin/bash
Your output will differ; keep comments and existing entries in mind while reviewing the change. Pick a candidate by its absolute path, then check it actually exists and is executable:
$ SHELL_PATH='/usr/local/bin/example-shell'
$ test -f "$SHELL_PATH" && test -x "$SHELL_PATH"
$ printf 'candidate: %s\n' "$SHELL_PATH"
candidate: /usr/local/bin/example-shell
Replace the placeholder with the real path. Do not add a wrapper, interpreter or test binary just because it happens to be executable: a path in /etc/shells tells login-related software that the program is suitable as a login shell, so check the program's own documentation and local policy first.
The manpage requires full pathnames: use /usr/bin/bash, not bash. This installed version does not reject a relative argument itself, so a typo such as bash can end up written in as a useless literal line. Treat the full-path requirement as a safety rule and verify the exact line afterwards.
3. Back up the file before the privileged change
This needs elevated privileges and creates your recovery copy. Do not overwrite the backup if you might need it later:
$ sudo install -m 0644 /etc/shells "/etc/shells.add-shell-backup.$(date +%Y%m%d%H%M%S)"
$ sudo ls -l /etc/shells /etc/shells.add-shell-backup.*
Record the backup path printed by ls. The timestamped copy is your undo point for this change; keep it until you have confirmed the affected login tools behave correctly.
4. Add the full pathname
Run the command with the exact path. This is the state-changing step and needs elevated privileges:
$ sudo /usr/sbin/add-shell /usr/local/bin/example-shell
$ printf 'add-shell exit status: %s\n' "$?"
add-shell exit status: 0
For several shells, put each full pathname in the same command:
$ sudo /usr/sbin/add-shell /usr/local/bin/example-shell /opt/tools/another-login-shell
There is no interactive confirmation. The command only adds an entry when that exact line is not already present, so running it again with the same path is a handy idempotence check, though it does not validate that the program is actually a suitable shell.
5. Verify the exact result
Check the file as a normal user. grep -Fx requires the whole line to match, so a partial match cannot give you a false success:
$ grep -Fx '/usr/local/bin/example-shell' /etc/shells
/usr/local/bin/example-shell
$ grep -Fx '/opt/tools/another-login-shell' /etc/shells
/opt/tools/another-login-shell
If a command returns status 1 and prints nothing, that exact entry is absent. Investigate the path, permissions and command error rather than editing the file by hand. Also check for an accidental relative entry:
$ awk 'NF && $1 !~ /^#/ && $1 !~ /^\// { print NR ":" $0 }' /etc/shells
That command should print nothing. Any output is a malformed non-comment entry worth reviewing; do not remove an existing line automatically, since another package or administrator may depend on it.
6. Test the workflow without touching the real file
The DPKG_ROOT environment variable makes add-shell use a different base directory for /etc/shells, which is handy for rehearsing a change or testing automation without root:
$ test_root=$(mktemp -d)
$ mkdir -p "$test_root/etc"
$ printf '%s\n' '# test shells' '/bin/sh' > "$test_root/etc/shells"
$ DPKG_ROOT="$test_root" add-shell /usr/local/bin/example-shell
$ cat "$test_root/etc/shells"
# test shells
/bin/sh
/usr/local/bin/example-shell
$ test ! -e "$test_root/etc/shells.tmp"
$ printf 'isolated test status: %s\n' "$?"
isolated test status: 0
The test also shows the temporary file is not normally left behind. Remove the temporary directory afterwards with your usual cleanup process. Do not set DPKG_ROOT during the real system change unless you deliberately intend to edit a staged root.
7. Undo the change if required
Restoring the backup is another privileged, state-changing action. Stop any process actively depending on the file first if your policy requires it, then use the exact backup path you recorded:
$ sudo install -m 0644 /etc/shells.add-shell-backup.YYYYMMDDhhmmss /etc/shells
$ if grep -Fx '/usr/local/bin/example-shell' /etc/shells; then echo 'entry still present'; else echo 'entry absent after restore'; fi
entry absent after restore
Replace the placeholder timestamp. If the backup is missing, or the file has changed since it was taken, stop and compare both files before choosing a recovery version. The restored file may hold other legitimate entries, so review it rather than trusting this one check to prove every detail is correct.
Common failure points
- No argument:
add-shellprints a usage line and exits with status 1. - Relative path: the installed command may append it literally even though the manpage requires full pathnames.
- Duplicate entry: the exact existing line is left alone; verify the final file rather than trusting command output.
- Permission denied: use
sudofor the command that changes/etc/shells, not for every read-only check. - Wrong root: an inherited
DPKG_ROOTchanges the file the command targets. Checkenv | grep '^DPKG_ROOT='before a production run if your automation sets environment variables.
Done means
- The candidate is real. It is an installed program with a full absolute pathname.
- You have a backup.
/etc/shellswas backed up and the backup path was recorded. - The command succeeded.
add-shellreturned status 0 undersudo. - The result is verified.
grep -Fxconfirms each intended entry, and no accidental relative entry was added. - You know the way back. Restoring the recorded backup is a known procedure if login behaviour is not acceptable.