Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm trying to write a bash script that

  • context switches to another user (root, in this case)
  • executes a set of commands to i. create a user (interactively prompting invoker for username) then ii. sets the password on that user
  • gets a return value
  • then carries on doing stuff, with that return value

I've so far tried an << EOF input (fig.1), a function (fig.2), and pulling / running a separate script (fig.3). However I haven't gotten the results I need. Any help here? Hope I've explained it clearly.

sudo su - << 'EOF'
# do stuff...
EOF

fig.1

function myfunc()
{
    local  myresult='some value'
    echo "$myresult"
}

fig.2

sudo su - -c bash <(curl -fksSL https://<my-script-location>.sh)

fig.3

share|improve this question

migrated from serverfault.com Jun 15 at 17:18

This question came from our site for professional system and network administrators.

3 Answers 3

up vote 3 down vote accepted
sudo su - << 'EOF'
# do stuff...
EOF

This works, but with a limitation: the script is passed to the shell on its standard input, so you don't get to use the standard input for something else such as reading the name of the user to create.

The simple way of retaining standard input is to pass the shell command as an argument instead.

Note that sudo su is redundant — sudo runs the specified command as root, and that's the sole purpose of su as well. Use sudo -i instead of sudo su - if you want to run a login shell as root — which is probably unnecessary here — or sudo sh to just run a shell as root.

sudo sh -c '
  # do stuff...
'

This makes it awkward to use a single quote in the shell snippet. You can use '\'' to put a single quote inside the single-quoted literal. If you want to retain the here document structure, there are several ways. The most straightforward is to pass the here document on a different descriptor. However this requires the closefrom_override option to be activated in the sudo configuration, which is not the case by default; by default, sudo closes all file descriptors other than stdin, stdout and stderr.

sudo -C 3 sh -c '. /dev/fd/3' 3<<'EOF'
# do stuff...
EOF

If you want your code to be portable to machines where this option isn't activated, you can read the script from a heredoc and pass it to the shell as an argument.

script=$(cat <<'EOF'
# do stuff...
EOF
)
sudo sh -c "$script"

Alternatively, if you want the sudoed script to be able to read from the terminal, you can pass the script body on standard input. A limitation of this approach is that it doesn't allow the input of the overall script to be redirected.

sudo sh <<'EOF'
exec </dev/tty
# do stuff...
EOF
share|improve this answer
    
A) Running sudo sh -c '. /dev/fd/3' 3<<'EOF' .. do stuff .. EOF gives me sh: 1: .: Can't open /dev/fd/3. B) If I try strace sudo sh -c '. /dev/fd/3' 3<<'EOF' ... EOF, I get this. C) This post looks relevant. But it's suggestions didn't work. Any hints? –  Nutritioustim Jun 21 at 14:33
    
@Nutritioustim My bad, I'd forgotten that sudo closes the file descriptor. See my edited answer. When a program is executed in strace, it loses privileges (the setuid bit is ignored). –  Gilles Jun 21 at 14:59
    
Ok, nice. That gets me a lot further. Whether using i) sudo sh -c "$script" or ii) sudo sh <<'EOF', is there a way to pass parameters and get return values, in and out of this method? –  Nutritioustim Jun 21 at 21:01
    
@Nutritioustim Sure: sh -c '…script…' param0 param1 param2 (i.e. param becomes $1, etc.). sudo returns the return code (value between 0 and 255) from the script. –  Gilles Jun 21 at 21:20
    
schweet. That does the trick - thanks. You can see the results here. I do have to do emacs-live manually, because it has it's own interactive shell that it runs. And I can't push file descriptor access down to within that script. But this has taken me pretty far. –  Nutritioustim Jun 22 at 1:53

Provided that the user invoking sudo can allocate a tty:

Defaults someuser:requiretty

ans has been granted permissions to execute the needed commands as root:

Cmnd_Alias STUFF = /usr/bin/passwd, /usr/sbin/useradd
someuser ALL = STUFF

a script containing sudo invocations will be interactive. You could pass the username as a positional argument:

sudo useradd $1
sudo passwd $1

and check the return status as usual with $?.

share|improve this answer
    
Hmm, I don't know the defaults command. Apt-get suggests gnustep-base-runtime, but that doesn't work. I tried these invocations (see here), all erroring out. –  Nutritioustim Jun 15 at 17:01
    
That's supposed to be in the sudoers configuration. –  dawud Jun 15 at 17:04
    
requiretty has nothing to do with being allowed to allocate a tty, but about sudo enforcing rules to disallow invocation outside of a tty context. –  Chris Down Jun 16 at 2:37
su -c '. /dev/fd/4' 4<<\SCRIPT
    ...automated stuff here...
    ....then for interactive...
    exec </dev/tty
SCRIPT

Should do it. Tailor this to meet your needs - but it will switch contexts and the shell's return status will be in $? when you're through with it.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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