74

There are already some existing questions asked here about running commands as another user. However, the question and answers focus on a single command instead of a long group of commands.

For example, consider the following script:

#!/bin/bash
set -e

root_command -p param1  # run as root

# these commands must be run as another user
command1 -p 'parameter with "quotes" inline'
command2 -p 'parameter with "quotes" inline'
command3 -p 'parameter with "quotes" inline'

There are a couple of important points to note here:

  • The final three commands must be run as another user using su or sudo. In the example there were three commands, but suppose that there were many more...

  • The commands themselves make use of single and double quotes.

The second point above prevents the use of the following syntax:

su somebody -c "command"

...since the commands themselves contain quotes.

What is the proper way to "group" the commands and run them under another user account?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nathan Osman
  • 71,149
  • 71
  • 256
  • 361

3 Answers3

173

Try this:

su somebody <<'EOF'
command1 -p 'parameter with "quotes" inline'
command2 -p 'parameter with "quotes" inline'
command3 -p 'parameter with "quotes" inline'
EOF

<< introduces a here-doc. The next token is the delimiter, and everything up to a line beginning with the delimiter is fed as standard input to the command. Putting the delimiter in single quotes prevents variable substitution within the here-doc.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • You, sir, are a genius. I had thought of using HEREDOC but couldn't find the correct syntax to make it work. Thanks! – Nathan Osman Jul 20 '13 at 03:43
  • Why is the `-c` flag not needed? Without `-c`, I get a permission denied error. –  Apr 11 '14 at 07:53
  • `-c` is used when the command is provided as an argument, rather than being read from standard input. – Barmar Apr 11 '14 at 14:22
  • 2
    If you encounter "su: must be run from a terminal", try `sudo su somebody <<'EOF'`. – Bohr Apr 20 '14 at 03:01
  • Is there a way to pass arguments into it? for example: `su somebody <<'EOF' echo $1 command1 -p 'parameter with "quotes" inline' EOF` doesn't work. :( – Seperman Sep 18 '14 at 05:57
  • 13
    If you use `< – Barmar Sep 18 '14 at 15:42
  • 7
    This will be useful to some: If you need the command to actually run in the user's full environment, as if they had logged in, which may include different paths, for example. Add they hyphen before 'somebody', like so: su - somebody ... – JJC Jun 10 '15 at 15:56
  • 4
    Make sure there is no whitespace before closing EOF (eg when calling from indented 'if' statement). Error is thrown otherwise - see http://www.tldp.org/LDP/abs/html/here-docs.html – Petr Újezdský Jan 15 '16 at 12:51
  • Please keep in mind that you can't use environment variables in HEREDOCs! I just stopped an rsync command that would have deleted all files on a server because `${GIT_PATH}/` was translated to `/` in the HEREDOC! – Patrizio Bekerle Sep 04 '18 at 10:36
  • @PatrizioBekerle It works fine for me. Environment variables are treated as ordinary variables by the shell. – Barmar Sep 04 '18 at 14:57
  • @Barmar, strange. Did you define them inside or outside the heredoc? Mine where defined outside (in the same bash script)... – Patrizio Bekerle Sep 04 '18 at 18:07
  • @PatrizioBekerle I did `export test_var=foo` then `cat < foo $test_var EOF` – Barmar Sep 04 '18 at 18:12
  • 1
    @PatrizioBekerle I did it interactively in the terminal, but I just tried it in a script as well, and they both worked. – Barmar Sep 04 '18 at 18:14
  • @Barmar, thank you for testing. I wonder what caused the variables not to be set for me. It would have been an elegant solution to use a heredoc. I fell back on doing a "su" on every command. – Patrizio Bekerle Sep 05 '18 at 04:15
  • @PatrizioBekerle Perhaps you should write a new question and show exactly what you did. – Barmar Sep 05 '18 at 17:16
  • @Barmar right now it's enough for me to know that there might be a way if I want to use it next time. :) – Patrizio Bekerle Sep 06 '18 at 04:33
8

I'm not that great with Bash-foo, so there is bound to be a more elegant way, but I've approached this problem in the past by using multiple scripts and a "driver".

E.g.,

Driver

#!/bin/bash
set -e

su root script1
su somebody script2

Script1

#!/bin/bash
set -e

root_command -p param1  # Run as root

Script2

#!/bin/bash
set -e

# These commands must be run as another user
command1 -p 'parameter with "quotes" inline'
command2 -p 'parameter with "quotes" inline'
command3 -p 'parameter with "quotes" inline'
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
stefancarlton
  • 1,717
  • 11
  • 12
0

This script checks if the current user running the script is the desired user. If not, then the script is re-executed with the desired user.

#!/usr/bin/env bash

TOKEN_USER_X=TOKEN_USER_X
USER_X=peter # other user!

SCRIPT_PATH=$(readlink -f "$BASH_SOURCE")

if [[ "$@" != "$TOKEN_USER_X" ]]; then

    ###### RUN THIS PART AS the user who started the script

    echo "This script is $SCRIPT_PATH"

    echo -n "Current user: "
    echo $USER

    read -p "insert: "
    echo "got $REPLY"

    su - $USER_X -c "$SCRIPT_PATH $TOKEN_USER_X" # execute code below after else (marked #TOKEN_USER_X)

else
    #TOKEN_USER_X -- come here only if script received one parameter TOKEN_USER_X

    ###### RUN THIS PART AS USER peter

    echo
    echo "Now this script is $SCRIPT_PATH"

    echo -n "Current user: "
    echo $USER

    read -p "insert: "
    echo "got $REPLY"

    exit 0
fi

echo
echo "Back to initial user..."
echo -n "Current user: "
echo $USER
Nathan
  • 8,093
  • 8
  • 50
  • 76
swift_dodo
  • 19
  • 1