Explaining behaviour of sudo bash "$0" "$@";

I've found this script part on the stackoverflow:

if [ $EUID != 0 ]; then
    sudo bash "$0" "$@";
    exit "$?";
 fi

I realized that sudo bash "$0" "$@"; is the only needed for me.

But the strange thing happens when I move this line outside the IF statement:

sudo bash "$0" "$@"; stops the code from running and only blinking dot stays with no progress further.

What happened here and how to make it properly work?

Not seeing from here what "$0" (the running programs name) and "$@" (a list of all comand line arguments to the running program) contains or any other parts of the environment you execute the snippet above in i can only speculate. The variable "EUID" is perhaps holding the "effective user ID", so the above reads: if you're not root already issue "sudo ...", which makes sense, because you need sudo to switch to root only if you aren't already root.

So my first guesses would be: either

  • you try to use sudo as root and you have some sudo rule in place (or missing) that prevents correct execcution of whatever you try to execute, or
  • your environment (especially "$0" and "$@" do not contain what you expect them to contain, maybe because of a switched context: switching to another user may cause settings to change, etc.) or
  • There might be a reason for the following exit-statement. Leaving it out may prevent ending of the execution.

But to debug your script i'd have to see it and probably know a bit about your surroundings (OS, shell, version, ....) to do so. As long as you don't provide that you will be on your own.

I hope this helps.

bakunin

1 Like

The Shell Version

vaidas@SATELLITE-L855:~/Desktop$ $SHELL --version
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http:://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

The Operating System and Hardware

vaidas@SATELLITE-L855:~/Desktop$ hostnamectl
   Static hostname: SATELLITE-L855
         Icon name: computer-laptop
           Chassis: laptop
        Machine ID: c1218e1a57f94029932e84f87d12c20f
           Boot ID: 5704dbfac79f4d66852cb7c22b1abf2f
  Operating System: Ubuntu 18.10
            Kernel: Linux 4.18.0-10-generic
      Architecture: x86-64

The Full Code

#!/bin/bash

# Checking for Permissions (Some funny reminder that the script requires to be ran with elevated permissions)
sudo -n true 2> /dev/null
if [ $? = 1 ]
  then echo "please run as root"
       echo "haha you have no power here";
       echo "You need to enter password";
  # else echo "Your password is cached or running as Root";
fi

# Execute the Script with elevated permissions
if [ $EUID != 0 ]; then
    sudo bash "$0" "$@";
    exit "$?";
fi


declare commandOutput=$(apt-add-repository multiverse);
if echo "$commandOutput" | grep -q "distribution component is already enabled"; then 
  echo "multiverse repository is already enabled"; 
fi

If you move the sudo outside the if (and therefore run it every time) the code will call itself every time, and call itself, and call itself..........

The exit is necessary because otherwise you will drop through and try to run the remainder of the script as a non-root user. An alternate to the exit might be to drop the current script and re-start it with sudo like this:-

if [ $EUID != 0 ]; then
    exec sudo bash "$0" "$@";
fi

I hope that this helps,
Robin

1 Like

Yep, that's infinite loop, thanks for pointing that out. That might be the case.

The final version looks like this(only added comments that explains bahaviour):
(Any suggestions to make it more readable would be great)

#!/bin/bash

# Checking if the user who executed this script, needs to type password to execute sudo
sudo -n true 2> /dev/null
if [ $? = 1 ]
  then echo "please run as root"
       echo "haha you have not power here";
       echo "You need to enter password";
  # else echo "Your password is cached or running as Root";
fi

# Checking if this .sh script is ran with sudo
if [ $EUID != 0 ]; then
    #Run this whole .sh script with sudo 
    exec sudo bash "$0" "$@"
fi

declare commandOutput=$(apt-add-repository multiverse);
if echo "$commandOutput" | grep -q "distribution component is already enabled"; then 
  echo "multiverse repository is already enabled"; 
fi


For starters, you almost never need to use $?, a normal if-statement will do. Written this way, you can kind of see the logic. "If not sudo, then ..."

Not sure if it's really a simplification but I'd add >&2, the traditional place for error messages. That way if someone uses this in a stream they can direct data output and error output separately.

You should have an exit after exec, in case the exec fails for whatever reason (i.e. path problems or something).

Then I'd label the section which is always intended to run as root.

#!/bin/bash

# If we can't run sudo without a password, complain and exit
if ! sudo -n true 2> /dev/null
then
       echo "please run as root" >&2
       echo "haha you have not power here" >&2
       echo "You need to enter password" >&2
       
       # Not sure if you want this or not?  Exits after printing error/warning
       exit 1
fi

# If EUID isn't zero, replace and re-run this script as root using sudo.
if [ "$EUID" -ne 0 ]
then
        exec sudo bash "$0" "$@"
        echo "exec failed" >&2
        exit 1
fi

#########################################
####### The below section always runs as root #########
#########################################

declare commandOutput=$(apt-add-repository multiverse);
if echo "$commandOutput" | grep -q "distribution component is already enabled"; then 
  echo "multiverse repository is already enabled" >&2
fi

The "replace and re-run" bit comes down to how exec really works: It causes the running program to replace itself with what you ask. Whether it succeeds or fails, after an

The >&2's make more likely that these messages get printed to the terminal, where these messages usually belong.

More could probably be done with apt-get, like checking its return value rather than just assuming it operates correctly.

1 Like

Added curly braces for better readability, your suggestions were very good.

#!/bin/bash

# If we can't run sudo without a password, complain and exit
if ! (sudo -n true 2> /dev/null)
then
       echo "please run as root" >&2
       echo "haha you have not power here" >&2
       echo "You need to enter password" >&2
fi

# If EUID isn't zero, replace and re-run this script as root using sudo.
if [ "$EUID" -ne 0 ]
then
        exec sudo bash "$0" "$@"
        echo "exec failed" >&2
        exit 1
fi

#########################################
####### The below section always runs as root #########
#########################################

declare commandOutput=$(apt-add-repository multiverse);
if echo "$commandOutput" | grep -q "distribution component is already enabled"; then 
  echo "multiverse repository is already enabled" >&2
fi

Those are round braces, not curly braces, and they create a sub-shell. Right now their only side-effect is wasting time and memory, but if you make a habit of that you'll discover that variable assignments and the like don't communicate outside them.

So they're not decorations and can cause problems if you make a habit of it.

1 Like

Bash programming is a true headache for beginners. May you suggest any reference book that has this kind of explanations if there is any? Conventions, best practices?

Conventions and best practices are to avoid inventing new syntax to make it "look better". Don't do that. Every time I've done that I've come to regret it when it fails to translate to some other shell.

Never paste shell code into a word processor. It will convert all your quote characters into nasty smart quote characters which to the shell don't count as quotes at all.

Arithmetic is best done inside RESULT=$((VAR+37)) statements instead of archaic let statements.

Numerical comparisons are best done inside if (( VAR1 > VAR2 )) statements instead of the archaic if [ "$VAR1" -gt "$VAR2" ]

String comparisons are still done inside if [[ "$STR" == "$STR2" ]] statements, note the doubled [[ ]]. The single [ ] mostly work the same but are archaic.

Backticks a la VAR=`command` are also archaic. To capture the output of a command, use VAR=$(command) instead.

Never use the $? special variable if you can possibly help it. If you're trying to use it, there's probably a logic structure that would work better, such as if , while , or case .

You can always plug a command into an if/while statement where you'd put a ((math)) or [[ bracket ]] statement, because to the shell they're mostly the same thing. case is different, case operates on a string.

Whitespace is unimportant inside (( )) brackets, but anywhere else, whitespace is crucial. This is wrong:

VAR = "value"

This is right:

VAR="value"

Single quotes ' and double-quotes " can both be used to denote strings but operate differently. Any text inside single quotes is not evaluated, where text inside double-quotes has variables substituted inside.

If you manage to force quote characters to be stored in a variable, they are not processed specially by the shell any more, they're just characters.

Avoid making too-long command | command | command | command | command chains. They are inefficient.

Avoid using external commands to process single strings, like VAR=$(echo "a b c" | awk '{ print $1 }') There's usually a shell builtin that will do a better job without the overhead of making an entire external program / language load, process, print, and quit.

The advanced bash programming guide is a good place to look things up since nobody really had everything memorized.