2
\$\begingroup\$

Script to prevent nested root shells shall do exactly as said, plus some niceties like an error message when someone runs it with some argument/option.


#!/bin/sh

set -o nounset; set -o errexit

error()
{
    printf >&2 '%b\n' "$@"
    exit 1
}

is_root()
{
    [ "$(id -u)" -eq 0 ]
}

start_root_shell()
{
    exec sudo -s
}

if [ "$#" -gt 0 ]; then
    error "Usage of $(basename "$0") script:\n" \
    'Starts a root shell, no argument accepted.'
fi

if is_root; then
    error 'You are already superuser!'
fi

start_root_shell
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

First, the things I like in this script:

  • Good use of the shell options to find simple mistakes. That's the first thing I do in my shell scripts.
  • Good error reporting - correctly using the error stream and exit status
  • Good use of quoted strings. You didn't miss the tricky one in $(basename "$0").
  • Everything is laid out well - it's clear and easy to read.

The observations I have are generally quite minor.


I don't see why we have two separate commands here:

set -o nounset; set -o errexit

It's simpler to write

set -o nounset  -o errexit

We're using an undefined conversion here:

printf >&2 '%b\n' "$@"

%b isn't among the conversions specified by POSIX. I think that %s should be fine here, if we change the single use of \n into a separate string argument (and this neatly fixes an obscure bug if the command file-name contains \):

    error "Usage of $(basename "$0") script:" \
          ''  \
          'Starts a root shell, no argument accepted.'

is_root and start_root_shell each contain a single command and are used just once, so the benefit of using functions is debatable. I'd lean towards inlining them, perhaps with a comment.


Modified version

The only change I'd insist upon if accepting this code would be to use a specified, portable conversion in the printf. The rest is just style.

#!/bin/sh

set -o nounset -o errexit

error()
{
    printf >&2 '%s\n' "$@"
    exit 1
}

if [ "$#" -gt 0 ]
then
    error "Usage of $(basename "$0") script:" \
          '' \
          'Starts a root shell, no argument accepted.'
fi

if [ "$(id -u)" -eq 0 ]
then
    error 'You are already superuser!'
fi

# Start the root shell
exec sudo -s
\$\endgroup\$
0
3
\$\begingroup\$

Apart from Toby Speight's answer which pointed me in the right direction, I myself have a point to make.


It would be handy if a user of the script had the possibility to change the actual root command, be it sudo -s, sudo -i, or even without sudo, directly using su.

The second change concerns the check for sudo being installed, which does not guarantee the user can use it, but at least something.

This (likely final) code is published on GitHub under MIT license.

Modified code incl. some other minor changes

#!/bin/sh

#--------------------------------------------------------------------------------
#--              Become superuser, but prevent nested root shells              --
#--                    Git: https://burian.work/root-shell                     --
#--                          Code: POSIX shell script                          --
#--                                License: MIT                                --
#--                                Version: 2.0                                --
#--                          Released on: 2024-Jul-04                          --
#--                    Copyright 2023-2024 Vlastimil Burián                    --
#--                   E-mail: [email protected]                   --
#--------------------------------------------------------------------------------

set -o nounset -o errexit

# You may customize these to e.g.: sudo -i, or su -, etc.

# The below constant variable must contain a single command,
# which you want to use to become root. Quotes not needed.
readonly root_command=sudo

# The below constant variable may contain more than one option
# for the command above. Quotes needed if more switches are used.
readonly root_cmd_opt=-s

# This routine could in theory be removed, it serves purpose for
# usage function. There would be a trailing space with no option
# used, for example with the root command su it would print `su `.
if [ -n "$root_cmd_opt" ]; then
    become_root_command="$root_command $root_cmd_opt"
else
    become_root_command=$root_command
fi

# Error message function, whatever you feed it goes to stderr and exits script right after.
error()
{
    printf >&2 '%s\n' "$@"
    exit 1
}

# This function prints usage to stderr, with the whole root command.
usage()
{
    error \
        "Usage of $( basename "$0" ) script:" \
        "" \
        "Starts a root shell using \`$become_root_command\`, no command-line argument accepted."
}

# Check for zero given arguments, or print usage.
if [ "$#" -gt 0 ]; then
    usage
fi

# This check is the sole reason for this script's existence.
# It prevents root shell nesting by checking if we are already root.
if [ "$( id -u )" -eq 0 ]; then
    error "You are already superuser. Nested root shells are disallowed."
fi

# If the root command is sudo, then it must be installed in order to work.
if [ "$root_command" = sudo ] && ! command -v sudo >/dev/null 2>&1; then
    error "Currently, \`sudo\` is set as the root command, and as such is required by this script, yet it appears not installed."
fi

# Start the almighty root shell with the above-defined commmand and options.
# Note that exec will replace current shell with the new one.
exec $become_root_command
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.