How to elevate privileges to root in bash? And then how to return back to exactly the previous state? My question is both about elevating/decreasing privileges and possible troubles with some apps, especially GUI apps (and an “environment”).

Say, some apps keep a separate profile for each user and root. GUI & profiles may be not the only problem with an “environment” if I run an app as root, regardless of whether this is their or OS’s bug or a feature.

Say, I need to run a script as root or already run a script as root (like in rc.local), so you might need to decrease the privileges or "completely" switch back to the normal user from there (su normaluser does not always work). To run initially a script with sudo -H and then to switch to another user? This does not always work as needed especially with GUI apps. I believe the trouble is with an "environment", and DISPLAY=:0 ... or DISPLAY=:0 gtk-launch ... (gtk-launch is likely bugged) may be yet not helpful.

Say, I have gedit with an opened document. If I run as just a normal user gedit /doc2, it opens with menu visible and in the already existing window in another tab. If I run it as root, it opens up in a separate window and without visible menu. If I run a script through sudo -H or su and try to run gedit /doc2 there as that normal user (again with sudo (-H) or su), then it works as if I did that as root, not that normal user. I tried also sudo options -l, -s, -i. With other GUI apps that caused far more serious troubles. Some GUI apps have different GUI or don’t run at all as a result. Sometimes runuser was helpful for me but not always. And heredoc format (sudo -H /bin/bash <<EOF <lines with commands> EOF) does not work as expected.

It is how many troubles this makes. For more than 1 year I could not find a good universal solution. So is there anything for elevation of privileges and returning back? Or other good workarounds?

And a full example just in case (run as sudo ./script.sh or with -H):

cd /somedir
some_commands # using the current directory, root privileges and setting some variables, and writing to somefile (better as normal user)
sudo -u normaluser /bin/bash -c 'gedit --encoding someencoding /somefile'`

If I run the following bash script as just ./script.sh

gedit --encoding someencoding /somefile

then gedit works as it should.

Just in case: it is about Ubunu 16.04 xenial, bash version 4.3.48.


I know I could run commands like

sudo sh -c ‘command1 $somevariable; command2’

or (I've found out it can be several lines)

sudo sh -c ‘command1 $somevariable command2’

or maybe something similar with bash. That might not be an option for a large set of commands and does not solve all the problems. And I absolutely don’t need to enter commands interactively. See also my answer.

P.S. I think Linux should user-friendly and easy to use.

  • It is probably easier to answer if you simply post a small script that doesn't work. Include the error messages displayed by system. – WinEunuuchs2Unix May 1 '19 at 12:26
  • I've already done this. It is about gedit. Also I described the situation with "cd" and the scope of variables if I call another script. maybe itself. Ok, I will make separate example. – J. Denver May 1 '19 at 12:29

To elevate privileges for few commands in script use sudo with heredoc syntax:

sudo /bin/bash <<EOF
    cd /somedir
    commandasroot1 "$possiblevariable"
nonrootcommand (and not in /somedir)

Testing cd: (working dir changed inside heredoc, but restores as it be before at end of heredoc)

leonid@DevSSD:~$ sudo bash <<EOF
> cd /tmp
> pwd
[sudo] password for leonid: 

One more example, shows how variables substitution work in heredoc:

leonid@DevSSD:~$ sudo bash <<EOF
    cd /tmp
    echo $PWD; echo \$PWD
[sudo] password for leonid: 

Update: example how you can get output into variable

leonid@DevSSD:~$ variable=$(sudo bash <<EOF
    cd /tmp
    echo $PWD; echo \$PWD
[sudo] password for leonid: 
leonid@DevSSD:~$ echo $variable
/home/leonid /tmp
| improve this answer | |
  • Maybe that heredoc would help but yet it is not working. Namely, cd does not work there, and I get $PWD unchanged even with hardcoded path. See my update above. – J. Denver May 1 '19 at 14:27
  • cd is working. You get wrong output because $PWD is resolved before sudo executes heredoc. Try pwd instead of 'echo $PWD' – LeonidMew May 1 '19 at 14:36
  • I run cd /tmp/ echo $(pwd) and get the path unchanged. – J. Denver May 1 '19 at 14:43
  • Updated again. In last example you can see how variables should be escaped, to get what you need. – LeonidMew May 1 '19 at 15:29
  • Thanks! That way it works. But yet that is not quite what I needed. See also my answer. The variant with sh requires less modification of the existing code because, e.g., echo $PWD or echo $(pwd) works. Maybe there is an analogical mode with bash. I like my workaround more. Yet the solution is not found. I thought there is a solution for such a bash stuff. – J. Denver May 1 '19 at 17:59

I don't have many scripts that elevate from user privileges to sudo (super user) powers. Ironically due to the fact your question is about gedit one of the scripts I have is called sgedit. It was created because gksu gedit is no longer supported and because root user can't set tab settings, font preferences, etc.


# NAME: sgedit
# PATH: /mnt/e/bin
# DESC: Run gedit as sudo using $USER preferences
# DATE: June 17, 2018.

# Must not prefix with sudo when calling script
if [[ $(id -u) == 0 ]]; then
    zenity --error --text "You cannot call this script using sudo. Aborting."
    exit 99

# Get user preferences before elevating to sudo
gsettings list-recursively | grep -i gedit | grep -v history | \
    grep -v docinfo | \
    grep -v virtual-root | grep -v state.window > /tmp/gedit.gsettings

sudoFunc () {

    # Must be running as sudo
    if [[ $(id -u) != 0 ]]; then
        zenity --error --text "Sudo password authentication failed. Aborting."
        exit 99

    # Get sudo's gedit preferences
    gsettings list-recursively | grep -i gedit | grep -v history | \
        grep -v docinfo | \
        grep -v virtual-root | grep -v state.window > /tmp/gedit.gsettings.root
    diff /tmp/gedit.gsettings.root /tmp/gedit.gsettings | grep '>' > /tmp/gedit.gsettings.diff
    sed -i 's/>/gsettings set/g; s/uint32 //g' /tmp/gedit.gsettings.diff
    chmod +x /tmp/gedit.gsettings.diff
    bash -x /tmp/gedit.gsettings.diff  # Display override setting to terminal
#    nohup gedit $@ &>/dev/null &
    nohup gedit -g 1300x840+1+1220 $@ &>/dev/null &
#              Set the X geometry window size (WIDTHxHEIGHT+X+Y).


FUNC=$(declare -f sudoFunc)
sudo -H bash -c "$FUNC; sudoFunc $*;"

exit 0

The script must be called in regular user mode. It copies gsettings for gedit from user profile to /tmp. Important settings like font size, line wrap, tab settings, convert tabs to spaces and plug-ins are copied.

Then sudo password is requested and status is elevated to root.

sudo -H is used for equivalent to gksu protection to prevent root powers from hammering user configuration files.

Next root configuration settings for gedit are inherited from calling user's profile that was copied into /tmp.

gedit is loaded as a background task and user is presented with sudo version of file opened. For example /etc/default/grub.

The sudo powers are immediately dropped and the command line prompt returns. However gedit is still running in a separate window with file opened for editing.

| improve this answer | |
  • Thanks! I have improved my answer and have refined my question. After a modification your script could possibly be a workaround for just one app. A good solution would look like to change 1-2 parameters in Ubuntu or better that option -H for sudo would do everything your script does but for all apps. And yet that would be not all. There would be a need to reduce privileges, say, to run from crontab or rc.local, because sudo -u normaluser ... does not always work as needed. – J. Denver May 2 '19 at 13:40

My answer. Yet a solution of how to just elevate and decrease privileges (actually, conceptually without even wrapping, existing separately) or to fully switch to root and back is required.

Basing on suggestions of me, LeonidMew and WinEunuuchs2Unix, here is the workaround. The -H option is usually a good choice so I include it.

There are two workarounds (bash can be replaced with sh):

1) sudo -H /bin/bash -c ‘commands’ and

2) sudo -H /bin/bash -c “commands” (as well as the heredoc format, sudo -H /bin/bash <<EOF <lines with commands> EOF).

1) Variables from outside are not visible inside automatically. They would have to be moved inside, be double-defined or be passed as arguments. I could not make or pass a short (re)declaration of an external function like $(declare -f functionname) inside (maybe it is yet possible), but it worked if I just moved it inside.

2) Only copies of variables from outside are passed inside. You will have to escape with \ constructs $(...), $PWD or other locally defined variables, AND commenting with # may not work (as with the #$(declare -f ...)). Arguments like $1 are the ones of the whole script and cannot be passed inside as local variables. Externally defined functions are not visible inside but can be redeclared inside like $(declare -f functionname).

In the both cases you can get output through files or a fast string-output from stdout (or several space-separated variables) through the wrapping res=$(...). You can see an example here: https://stackoverflow.com/a/23567255 . Though after the wrapping all EOLs are converted into spaces. Maybe export, designed to pass variables to subprocesses, will help to avoid that wrapping or the usage of files, but somehow it didn’t work for me.

The case 1) seems to be the best default choice and will most likely require less modifications of the existing code, contrary to the case 2) when a careful modification of the existing code will usually be required. Maybe you will find the both cases useful simultaneously.

This is my simple example:

tmpdir=/tmp/ tmpfile=/tmp/tmpfile res=$(sudo -H bash -c 'tmpdir=$1; tmpfile=$2 echo "$tmpdir;" cd $ tmpdir echo $(pwd) echo "A string from file" > $tmpfile ' - $tmpdir $tmpfile) echo $res arr=($res) echo ${arr[0]} # Hellooo echo ${arr[1]} # world; echo ${arr[*]} # whole array echo ${#arr[*]} # number of items n=0 echo ${#arr[$n]} # length of the n-th element cat $tmpfile sudo sh -c 'rm -f $2' - - $tmpfile

| improve this answer | |
  • 1
    Yes, you can pass var by export. Passing output by tmp file considered harmful – LeonidMew May 1 '19 at 18:52
  • Somehow I could not in my case. In your case it was not needed and didn't allow me to change a variable from inside. Yes, a tmp file can cause security problem. As well as passing arguments or especially all arguments to not trusted commands. – J. Denver May 1 '19 at 18:59
  • Make temp file with tmphost=$(mktemp), and trap on exit: trap 'rm "$tmphost"' EXIT – LeonidMew May 1 '19 at 19:13
  • I don't like mktemp because it generates more lines... And it does not let chose location of the temporary file, say on which drive, in case I will decide to store it permanently. – J. Denver May 1 '19 at 19:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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