How do I use su to execute the rest of the bash script as that user?
Asked Answered
C

9

188

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)?

Common answered 1/1, 2010 at 9:38 Comment(0)
T
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

Tallyman answered 1/1, 2010 at 9:42 Comment(6)
I have a similar issue, but I wanted to run chsh for the other users. My issue is listed here at https://mcmap.net/q/137092/-how-to-execute-chsh-using-a-bash-script-i-put-up-in-github/80353 How do I adapt your answer in my situation?Exuviae
I did this - but it still asked me for a password.Transude
@Transude are you sure you got the usernames right way round?Tallyman
I did - it turns out that I also needed to allow usage of /bin/bash as well.Transude
Whether you use sudo or su is of secondary importance, though sudo is a lot more secure and convenient.Highhanded
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.Nacred
C
177

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)

Clayberg answered 11/7, 2014 at 11:35 Comment(9)
This answer worked best. I recommend to use option -i to get someuser's expected environment.Malvoisie
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.Nacred
Epic solution !Anechoic
How to make variables available in herdoc's scope?Contuse
on AIX it throws this error ksh: sh: 0403-006 Execute permission denied.Baribaric
@dotslashlu, it is not possible directly since heredoc contents will be executed within a subshell, and sudo drops all environment variables by default. But in fact heredoc is a plain string passed to bash, and current environment variables are expanded in it. So if you set MYVAR=42 and then use heredoc ... bash <<EOF ↓ echo $MYVAR ↓ EOF (↓ denoting line break) then you will see 42 printed.Approve
Hi @dan-dascalescu what if the user we are changing to has a password that needs to be entered?Fauman
@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.Clayberg
This is ONLY for root user, and if you are not a root and need to enter a password? No answerSketchy
T
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

Tallyman answered 1/1, 2010 at 9:42 Comment(6)
I have a similar issue, but I wanted to run chsh for the other users. My issue is listed here at https://mcmap.net/q/137092/-how-to-execute-chsh-using-a-bash-script-i-put-up-in-github/80353 How do I adapt your answer in my situation?Exuviae
I did this - but it still asked me for a password.Transude
@Transude are you sure you got the usernames right way round?Tallyman
I did - it turns out that I also needed to allow usage of /bin/bash as well.Transude
Whether you use sudo or su is of secondary importance, though sudo is a lot more secure and convenient.Highhanded
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.Nacred
W
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" 
Wilder answered 1/1, 2010 at 9:53 Comment(6)
This is the only correct answer. The sudo is not necessary for this.Crofter
You may need also to provide shell with su -s /bin/bash.Nelda
best solution for root usersArchibold
Best answer for those who don't have sudo installed by default (I'm looking at you AIX)Nacred
Great answer for those Debian users which don't want to use sudo or add their user to the sudoers fileCisterna
This is working also as expected in Jenkins when you need to spawn a new Docker container, execute some commands, and then want a separate user to go on.Mathematician
A
70

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"
...
Approve answered 30/4, 2015 at 13:36 Comment(10)
I wonder why this is not higher rated. It is solving the original question the best while keeping everything in bash.Duluth
Or runuser -u $user -- "$@", as stated in su(1)Surfbird
Yes, runuser can be used in an opposite situation - i.e. to drop root privileged.Approve
exec su "$user" "$0" -- "$@"Blatt
Thanks @macieksk, nice catch. Will update. The -- is really useful.Approve
Presented approach is the best of the all and is complete. All is written inside single script, and all uses bash. Effectively, this script is runned twice. At first script test it is root, then prepare environment, and change user by su. But do it with exec command, then there is only single script instance. With second loop, phrase if/fi is ommited, then script directly do prepared action. In this example it is echo. All of other approaches resolve problem only partially. Of course this script can be run under sh not bash, but we must test $HOME special variable, not $UIDBreakwater
@Znik, if we test $HOME then it may break if root's home directory was set to something other than /root in /etc/passwd. This is sometimes the case in some embedded systems.Approve
This seems to use the users default shell instead of the one specified in the shebang. Anyone has an idea how to change this?Selfsupporting
Replacing the exec line with exec sudo -i -u "$user" $( readlink -f "$0" ) -- "$@" works for me.Selfsupporting
@Selfsupporting su has a parameter -s, --shell <shell>, which is described as "run <shell> if /etc/shells allows it". I used -s /usr/bin/bash and it worked.Retaliation
U
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
Understanding answered 25/1, 2012 at 6:37 Comment(5)
You may want to use "sudo -i -u ..." to ensure that stuff like $HOME is set correctly.Boman
Sorry for noob question, but whats an id here?Dwt
@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 idStereo
@MohammedNoureldin Thanks!Dwt
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.Nacred
A
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.

Afebrile answered 1/1, 2010 at 9:42 Comment(3)
That won't work though - the cd will be lost after the first sudo has finished executing.Wilder
Actually, you can't even call cd directly because it is not an external command.Afebrile
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.Nacred
M
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');
Meyer answered 1/1, 2010 at 11:33 Comment(2)
-1 as It IS possible with sudo to temporarily gain other user's rights.Tallyman
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.Meyer
U
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 &
Unlicensed answered 29/7, 2014 at 17:58 Comment(0)
M
-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.

Mulder answered 17/5, 2017 at 9:39 Comment(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.Nacred
@Nacred 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.Mulder
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"Romonaromonda
@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.Mulder

© 2022 - 2024 — McMap. All rights reserved.