Home / Alt manpages / update-shells(8)

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

Sync /etc/shells with update-shells

A shell an administrator added by hand can vanish the moment a package touches /etc/shells, unless update-shells knows to leave it alone. 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 not recorded as packaged is preserved when the command rebuilds /etc/shells, including something like /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, useful for testing a proposed layout or 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. What matters 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

Tip

--no-act is not a permission bypass. The script still creates temporary files beside the target files, so a normal user may get a permission error on a real /etc even though the final files are never 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 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

Checkpoint

The final command should 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

  • Version checked. You checked the installed debianutils version and local manual.
  • Inputs reviewed. You reviewed the package shell inputs and current /etc/shells.
  • Preview run. You ran a verbose --no-act preview.
  • Applied deliberately. You applied the change with sudo only after reviewing the preview.
  • State confirmed. The second no-act run is quiet and /var/lib/shells.state matches the packaged set.