Home / Alt manpages / update-shells(8)

  • update-shells(8)
  • Admin command
  • linux

Keep /etc/shells in Step with Debian Packages

You will use update-shells to preview and apply changes to the list of valid login shells, while keeping track of entries that an administrator added locally. The command comes from debianutils; the machine used for these examples has package version 5.17build1.

Allow about ten minutes. You need a shell and debianutils. Previewing a real host needs write access to its temporary files, and applying the update needs root. The guide uses a temporary root first, so you can see the behaviour without changing the host.

1. Check the installed contract

Read the local manual and identify the binary before changing anything:

$ man 8 update-shells
$ command -v update-shells
/usr/sbin/update-shells
$ dpkg-query -W -f='${Package} ${Version}\n' debianutils
debianutils 5.17build1

update-shells reads packaged shell paths from /usr/share/debianutils/shells.d, updates /etc/shells, and records the packaged set in /var/lib/shells.state. It is not a general-purpose editor for /etc/shells.

Checkpoint

Do not edit /etc/shells yet. First decide whether you need to inspect the proposed result or apply it.

2. Understand what is managed

Inspect the inputs as an ordinary, read-only operation:

$ sed -n '1,120p' /usr/share/debianutils/shells
$ find /usr/share/debianutils/shells.d -maxdepth 1 -type f -print -exec sed -n '1,40p' {} \;
$ sed -n '1,120p' /etc/shells
$ sed -n '1,120p' /var/lib/shells.state

The package template and files in shells.d describe shells supplied by packages. The state file lets the command distinguish an old packaged entry from a local addition. A line that was not recorded as packaged is preserved when the command rebuilds /etc/shells, including a local entry such as /usr/local/bin/my-shell.

That distinction is the main safety boundary. If you want to add or remove a local shell, use the appropriate account-management procedure and then inspect the resulting file. Do not place a package path into shells.d by hand unless you are maintaining packaging integration.

3. Preview with a disposable root

--root makes the command operate below a directory. This is useful for testing a proposed layout or for maintaining a chroot. The root must contain the relevant etc, var/lib and Debianutils data paths.

$ ROOT_DIR='/tmp/my-login-shell-root'
$ mkdir -p "$ROOT_DIR/etc" "$ROOT_DIR/var/lib" \
    "$ROOT_DIR/usr/share/debianutils/shells.d"
$ cp /usr/share/debianutils/shells \
    "$ROOT_DIR/usr/share/debianutils/shells"
$ cp /usr/share/debianutils/shells.d/* \
    "$ROOT_DIR/usr/share/debianutils/shells.d/"
$ printf '%s\n' '# test root' '/bin/sh' '/usr/local/bin/my-shell' \
    > "$ROOT_DIR/etc/shells"
$ printf '%s\n' '/bin/sh' > "$ROOT_DIR/var/lib/shells.state"
$ update-shells --root "$ROOT_DIR" --no-act --verbose
adding shell /bin/bash
adding shell /bin/rbash
adding shell /usr/bin/dash
adding shell /usr/bin/tmux

The exact additions depend on the package files installed on your system. The important result is that --no-act reports changes without replacing the target files:

$ sed -n '1,40p' "$ROOT_DIR/etc/shells"
# test root
/bin/sh
/usr/local/bin/my-shell

Distraction trap: --no-act is not a permission bypass. The script still creates temporary files beside the target files. A normal user may therefore get a permission error on a real /etc, even though the final files are not meant to be moved into place.

4. Apply a tested update

Applying the update replaces ROOT_DIR/etc/shells and writes ROOT_DIR/var/lib/shells.state. Treat this as a system configuration change: check the verbose preview first, then run the same command without --no-act with elevated privileges.

$ sudo update-shells --verbose
adding shell /bin/bash
adding shell /bin/rbash
adding shell /usr/bin/dash
adding shell /usr/bin/tmux

No output means that no packaged shell needed adding or removing. The command does not restart a service, but authentication tools can consult /etc/shells, so check the result before relying on it for new logins.

Verify both files immediately:

$ getent shells
$ sed -n '1,120p' /var/lib/shells.state
$ sudo update-shells --no-act --verbose

The final command should normally print nothing after a successful update. If it proposes a change, investigate the package files, state file and local edits before repeating the write.

5. Handle removals and recovery

When a packaged shell disappears, update-shells can remove its old entry from /etc/shells if that entry is present in the state it manages. Entries not recorded as packaged are retained. That is why manually deleting lines from /var/lib/shells.state is unsafe: it destroys the information needed to classify the next change.

Warning

Do not run the command after hand-editing the state file as a way to undo a result. For a mistaken update, restore the affected files from your configuration backup, then run a no-act preview and compare it with the restored files. If the only problem is a local shell entry, add it back to /etc/shells using your normal change-control process and leave the package state alone.

If a preview fails, capture the error and status without chaining another command over it:

$ sudo update-shells --no-act --verbose
$ status=$?
$ printf 'update-shells status: %s\n' "$status"
update-shells status: 0

A non-zero status means the proposed operation was not verified. Check that the root paths exist, that the target directories are writable by the elevated command, and that the shell data files are readable. Do not treat a failed preview as evidence that no update is needed.

Done means

  • you checked the installed debianutils version and local manual;
  • you reviewed the package shell inputs and current /etc/shells;
  • you ran a verbose --no-act preview;
  • you applied the change with sudo only after reviewing the preview;
  • the second no-act run is quiet and /var/lib/shells.state matches the packaged set.