sudo is very useful when working interactively in the shell. However, when you are scripting workflows then you don’t want to encounter the interactive prompt for authentication.
- Part 1: Demystifying
- Part 2: The
- Part 3:
sudoand Scripting (this post)
- Part 4: The Authorization Database
sudo in Scripts
You will write scripts that require
root privileges to perform their tasks. Many novice scripters will simply add the
sudo command to their scripts. For example:
#!/bin/bash timeserver="time.apple.com" echo "Setting Time Server to: $timeserver" # note: sudo is superfluous sudo systemsetup -setnetworktimeserver "$timeserver" sudo systemsetup -setusingnetworktime on
When you invoke this script from the command line, it will actually work as expected, since the first
sudo will prompt the password and the second
sudo will use the cached credentials.
In many cases where the script already has
root privileges, it will work also, because
sudo when run as
root will not prompt for authorization again.
However, when this script is sent with Remote Desktop or run in a different context without user interaction to authorize
sudo, the script will stall and eventually fail.
In most contexts, the scripts should already be running with
root privileges so
sudo in the script is not necessary.
#!/bin/bash timeserver="time.apple.com" echo "Setting Time Server to: $timeserver" systemsetup -setnetworktimeserver "$timeserver" systemsetup -setusingnetworktime on
You should not use
sudo in your management scripts. When commands inside a script require
root privileges, you should invoke the entire script with
$ sudo ./settimeserver.sh
or you deploy and execute the script through other methods that provide
root privileges. (Remote Desktop: run as user root, installation scripts, LaunchDaemons, management systems, etc.)
root Privileges in Scripts
When you write scripts that require
root privileges, you may want to test whether it is actually running as
root early in the script or exit gracefully with an error or warning. As usual with Unix, there are many ways to achieve this, but the recommended one is to check the
EUID (effective user id) environment variable. For the
root user the
#!/bin/bash # test if root if [[ $EUID -ne 0 ]]; then >&2 echo "script requires super user privileges, exiting..." exit 1 fi # continue with important things here echo "I am root"
Running a Process as Another User
Most administrator scripts are run in a context where they run with super user privileges. Often they require root privileges.
On the other hand, when you need to affect settings or processes in a user’s context or login session, you may need to run commands as a specific user from a script running as
root. It’s the opposite problem of gaining
You can use
sudo -u user command or
su user -c command to run a command as a different user. However, the
launchd man page warns us:
On Darwin platforms, a user environment includes a specific Mach boot strap subset, audit session and other characteristics not recognized by POSIX. Therefore, making the appropriate setuid(2) and setgid(2) system calls is not sufficient to completely assume the identity for a given user. Running a service as a launchd agent or a per-user XPC service is the only way to run a process with a complete identity of that user.
So it is safest use the Darwin/macOS native mechanism to launch a process as another user. Enter
launchctl. You can use the the
launchctl asuser verb to execute a script or command as a different user.
uid=$(id -u "$username") launchctl asuser "$uid" /path/to/command arguments
launchctl command takes a user’s numerical ID or UID rather than the short name as an argument. You can get a user’s UID with the
id -u username command.
launchctl asuser does not launch the command in the context of a new shell environment. You cannot rely on environment variables being set in that context. This is especially relevant for the
Note also that the
asuser verb of
launchctl is listed among the deprecated functions of
launchctl. However, there is no replacement for the functionality yet.
Getting the Current User
Most of the time you will not know which user you need to run as, when you write a script, but need to run as the ‘currently logged in user’. There are many unix-y ways of determining the current user. However, once again there are edge cases in macOS where some of the traditional methods fail. (Mainly concerning Fast User Switching)
The ‘official’, method is to use the SystemConfiguration framework, and from a script this is easiest from within
loggedInUser=$(/usr/bin/python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None]); username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "\n");')
This will return the currently active user, even when multiple users are logged in with Fast User Switching. When no user is logged and the system is logging in the value returned will be
"". Your scripts have to cover this case as well.
You can use this code snippet as a template:
# get the current user loggedInUser=$(/usr/bin/python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None]); username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "\n");') # test if a user is logged in if [[ $loggedInUser != "" ]]; then # get the uid uid=$(id -u "$loggedInUser") # do what you need to do launchctl asuser "$uid" /path/to/command arguments fi
AppleScript has a special function when invoking shell commands for gaining super user privileges. When you run a shell command from AppleScript with the
do shell script command you can add
with administrator privileges to have the script prompt for admin user authentication and run the command as
do shell script "whoami" do shell script "whoami" with administrator privileges
Will run like this:
do shell script "whoami" --> "armin" do shell script "whoami" with administrator privileges --> "root"
sudo the credentials for the first command run with administrator privileges will also be cached, so you will not get mulitple prompts, unless the script runs for a long time.
This is useful for providing tools and workflows from within AppleScript for interactive use. However, as with
sudo you have to keep in mind that some contexts in which a script may be run does not allow for user interaction and then you have to find other means of elevating privileges.
AppleScript is often used to communicate with other applications and/or the user interface. However, when run with
root privileges AppleScripts are often prohibited from connecting to other process or present user interface, such as dialogs. When you really need to do this, you have to use
launchctl asuser to change the user the script is run as:
launchctl asuser "$uid" /usr/bin/osascript -e 'display dialog "Do you really want to do this?"
Beyond the Shell
sudo allows you to gain super user privileges from the interactive shell. LaunchDaemons, installer Packages, management systems and other tools will run scripts as
root when required. This covers many cases where administrators need to influence the system on macOS.
However, for most users, macOS is exclusively the graphical interface. Users can authorize to perform certain tasks when they are administrator users, like unlocking a Preference Pane or running an installer package.
These tasks are not controlled by
sudo but by a separate mechanism. The data for that mechanism is stored in the authorization database, which we will cover in the next post.