191

I've written a script that takes, as an argument, a string that is a concatenation of a username and a project. The script is supposed to switch (su) to the username, cd to a specific directory based upon the project string.

I basically want to do:

su $USERNAME;  
cd /home/$USERNAME/$PROJECT;  
svn update;  

The problem is that once I do an su... it just waits there. Which makes sense since the flow of execution has passed to switching to the user. Once I exit, then the rest of the things execute but it doesn't work as desired.

I prepended su to the svn command but the command failed (i.e. it didn't update svn in the directory desired).

How do I write a script that allows the user to switch user and invoke svn (among other things)?

9 Answers 9

182

Much simpler: use sudo to run a shell and use a heredoc to feed it commands.

#!/usr/bin/env bash
whoami
sudo -i -u someuser bash << EOF
echo "In"
whoami
EOF
echo "Out"
whoami

(answer originally on SuperUser)

9
  • 7
    This answer worked best. I recommend to use option -i to get someuser's expected environment.
    – not2savvy
    Commented Jun 13, 2017 at 15:04
  • 3
    sudo may be convenient but it's no good if its not out of the box (like on AIX). su -c 'commands' is the correct answer.
    – Tricky
    Commented Mar 22, 2018 at 5:07
  • 3
    Epic solution !
    – 3bdalla
    Commented Nov 7, 2018 at 14:59
  • How to make variables available in herdoc's scope?
    – dotslashlu
    Commented Nov 23, 2018 at 9:12
  • 1
    @DJ_Stuffy_K: it doesn't matter. You only need to enter the password of the currently logged in user, which needs root privileges. See differences between sudo and su. Commented Feb 23, 2021 at 11:49
109

The trick is to use "sudo" command instead of "su"

You may need to add this

username1 ALL=(username2) NOPASSWD: /path/to/svn

to your /etc/sudoers file

and change your script to:

sudo -u username2 -H sh -c "cd /home/$USERNAME/$PROJECT; svn update" 

Where username2 is the user you want to run the SVN command as and username1 is the user running the script.

If you need multiple users to run this script, use a %groupname instead of the username1

6
  • I have a similar issue, but I wanted to run chsh for the other users. My issue is listed here at stackoverflow.com/q/15307289/80353 How do I adapt your answer in my situation?
    – Kim Stacks
    Commented Mar 10, 2013 at 3:32
  • I did this - but it still asked me for a password.
    – Hippyjim
    Commented Oct 19, 2013 at 16:30
  • @Hippyjim are you sure you got the usernames right way round?
    – Kimvais
    Commented Oct 19, 2013 at 17:23
  • 1
    I did - it turns out that I also needed to allow usage of /bin/bash as well.
    – Hippyjim
    Commented Oct 20, 2013 at 12:27
  • 4
    Whether you use sudo or su is of secondary importance, though sudo is a lot more secure and convenient.
    – tripleee
    Commented Feb 24, 2015 at 8:24
75

Here is yet another approach, which was more convenient in my case (I just wanted to drop root privileges and do the rest of my script from restricted user): you can make the script restart itself from the correct user. This approach is more readable than using sudo or su -c with a "nested script". Let's suppose it is started as root initially. Then the code will look like this:

#!/bin/bash
if [ $UID -eq 0 ]; then
  user=$1
  dir=$2
  shift 2     # if you need some other parameters
  cd "$dir"
  exec su "$user" "$0" -- "$@"
  # nothing will be executed from root beyond that line,
  # because exec replaces running process with the new one
fi

echo "This will be run from user $UID"
...
10
  • 7
    I wonder why this is not higher rated. It is solving the original question the best while keeping everything in bash.
    – SirVer
    Commented Jun 4, 2016 at 15:54
  • 3
    Or runuser -u $user -- "$@", as stated in su(1)
    – cghislai
    Commented Jul 29, 2018 at 15:44
  • 1
    Yes, runuser can be used in an opposite situation - i.e. to drop root privileged.
    – MarSoft
    Commented Jul 31, 2018 at 17:43
  • 1
    exec su "$user" "$0" -- "$@"
    – macieksk
    Commented Dec 13, 2018 at 0:41
  • 2
    Replacing the exec line with exec sudo -i -u "$user" $( readlink -f "$0" ) -- "$@" works for me.
    – darkdragon
    Commented May 26, 2020 at 22:00
71

You need to execute all the different-user commands as their own script. If it's just one, or a few commands, then inline should work. If it's lots of commands then it's probably best to move them to their own file.

su -c "cd /home/$USERNAME/$PROJECT ; svn update" -m "$USERNAME" 
6
  • 8
    This is the only correct answer. The sudo is not necessary for this.
    – helvete
    Commented Jan 26, 2016 at 14:28
  • 1
    You may need also to provide shell with su -s /bin/bash.
    – mixel
    Commented Jan 5, 2017 at 14:11
  • best solution for root users
    – rfinz
    Commented Jul 27, 2017 at 16:31
  • 1
    Best answer for those who don't have sudo installed by default (I'm looking at you AIX)
    – Tricky
    Commented Mar 22, 2018 at 4:43
  • Great answer for those Debian users which don't want to use sudo or add their user to the sudoers file
    – iago
    Commented May 17, 2022 at 9:16
59

Use a script like the following to execute the rest or part of the script under another user:

#!/bin/sh

id

exec sudo -u transmission /bin/sh - << eof

id

eof
5
  • 11
    You may want to use "sudo -i -u ..." to ensure that stuff like $HOME is set correctly. Commented Aug 8, 2014 at 14:07
  • 2
    Sorry for noob question, but whats an id here? Commented Dec 23, 2016 at 15:13
  • 2
    @NitinJadhav, he used it here just to show the ID of the current user, the ID of root is 0, so the first id will show you some number, but the second one will definetly show 0 (because the second one was executed inside a block run by root). You can user whoami instead of id which will return the name instead of the id Commented Dec 24, 2016 at 14:17
  • @MohammedNoureldin Thanks! Commented Dec 29, 2016 at 17:06
  • sudo may be convenient but it's no good if its not out of the box (like on AIX). su -c 'commands' is the correct answer.
    – Tricky
    Commented Mar 22, 2018 at 5:07
8

Use sudo instead

EDIT: As Douglas pointed out, you can not use cd in sudo since it is not an external command. You have to run the commands in a subshell to make the cd work.

sudo -u $USERNAME -H sh -c "cd ~/$PROJECT; svn update"

sudo -u $USERNAME -H cd ~/$PROJECT
sudo -u $USERNAME svn update

You may be asked to input that user's password, but only once.

3
  • That won't work though - the cd will be lost after the first sudo has finished executing. Commented Jan 1, 2010 at 9:47
  • Actually, you can't even call cd directly because it is not an external command.
    – iamamac
    Commented Jan 1, 2010 at 10:03
  • sudo may be convenient but it's no good if its not out of the box (like on AIX). su -c 'commands' is the correct answer.
    – Tricky
    Commented Mar 22, 2018 at 5:09
7

It's not possible to change user within a shell script. Workarounds using sudo described in other answers are probably your best bet.

If you're mad enough to run perl scripts as root, you can do this with the $< $( $> $) variables which hold real/effective uid/gid, e.g.:

#!/usr/bin/perl -w
$user = shift;
if (!$<) {
    $> = getpwnam $user;
    $) = getgrnam $user;
} else {
    die 'must be root to change uid';
}
system('whoami');
2
  • -1 as It IS possible with sudo to temporarily gain other user's rights.
    – Kimvais
    Commented Jan 1, 2010 at 12:22
  • 5
    It's not possible in the sense that the user the shell script itself runs as can't be changed (which is what the original question asked). Invoking other processes with sudo doesn't change who the script itself is running as.
    – P-Nuts
    Commented Jan 1, 2010 at 12:53
2

This worked for me

I split out my "provisioning" from my "startup".

 # Configure everything else ready to run 
  config.vm.provision :shell, path: "provision.sh"
  config.vm.provision :shell, path: "start_env.sh", run: "always"

then in my start_env.sh

#!/usr/bin/env bash

echo "Starting Server Env"
#java -jar /usr/lib/node_modules/selenium-server-standalone-jar/jar/selenium-server-standalone-2.40.0.jar  &
#(cd /vagrant_projects/myproj && sudo -u vagrant -H sh -c "nohup npm install 0<&- &>/dev/null &;bower install 0<&- &>/dev/null &")
cd /vagrant_projects/myproj
nohup grunt connect:server:keepalive 0<&- &>/dev/null &
nohup apimocker -c /vagrant_projects/myproj/mock_api_data/config.json 0<&- &>/dev/null &
-2

Inspired by the idea from @MarSoft but I changed the lines like the following:

USERNAME='desireduser'
COMMAND=$0
COMMANDARGS="$(printf " %q" "${@}")"
if [ $(whoami) != "$USERNAME" ]; then
  exec sudo -E su $USERNAME -c "/usr/bin/bash -l $COMMAND $COMMANDARGS"
  exit
fi

I have used sudo to allow a password less execution of the script. If you want to enter a password for the user, remove the sudo. If you do not need the environment variables, remove -E from sudo.

The /usr/bin/bash -l ensures, that the profile.d scripts are executed for an initialized environment.

4
  • sudo may be convenient but it's no good if its not out of the box (like on AIX). su -c 'commands' is the correct answer.
    – Tricky
    Commented Mar 22, 2018 at 5:11
  • @Tricky Perhaps read the full answer, it already suggests removing sudo. In fact, sudo is not that simple, in a lot of cases you need even sudo -E and a configuration entry in sudoers.d to allow execution without tty with !requiretty. But there are a lot of cases, where sudo is necessary for automatic called scripts, where a password dialog might interfere. Thus I would not remove it from a standard solution. Commented Mar 23, 2018 at 9:36
  • The code in question is outright buggy -- it'll break script names or arguments with spaces; keep in mind that putting "$@" inside of a string means that arguments past the first one are appended into a separate string, not included in the -c argument. If you wanted to make it safe, you might printf -v arg_q '%q ' "$0" "$@" and then use su "$USERNAME" -c "/usr/bin/bash -l $arg_q" Commented Jun 26, 2018 at 16:47
  • @CharlesDuffy You are right! I simplified my production script too much for this answer :-( Just adding a COMMANDARGS=$@ solves the problem with -c. Arguments with spaces were not a problem before, but I implemented your good input. I just had to do some experiments to make it work. I've edited the question and hopefully I did not paste another bug. Thanks for your comment, humiliating but necessary. Commented Jun 27, 2018 at 17:28

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