I recently posted an article on how to get the current user in macOS. If you read through that entire post, it’ll be obvious that it was actually an excuse to write about the deprecation of python 2 and /bin/bash
and the situation with sh
‘compatible’ code.
The ‘tl;dr’ of that post was to stop using the python one-liner and use this scutil
based one-liner, based on Erik Berglund’s post, instead.
zsh and bash scripts
loggedInUser=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' )
sh scripts
loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ && ! /loginwindow/ { print $3 }' )
From now on, I will call these the ‘SystemConfiguration’ solution (this also includes the python one-liner). Both the python and the scutil
based one-liners go through the SystemConfiguration framework to get at the desired information: which user is using the ‘Console?’
The ‘console’ in this case is an abstract definition, which encompasses the screen output and most inputs. I could not find a clear definition of what the ‘Console’ is exactly on macOS. (If you can point me to one, I’d be grateful.) But it serves to think of the console as all the processes, inputs and outputs attached to a user’s login session.
The other one-liner
There is another solution that keeps popping up in MacAdmins Slack and blog posts.
loggedInUser=$(stat -f %Su /dev/console)
(The stat
solution.) This certainly looks much shorter and simpler. But does it return the same in all situations? Is it actually the same?
Testing the Edge
I have done some testing with Fast User Switching and a second remote Screen Sharing session. These are the weird edge cases for which the original python one-liner was devised.
I had a remote ssh session open from my main Mac while I logged two test users in and out of a second test Mac. I ran the commands for the SystemConfiguration and stat solutions in the ssh session, and compared the return values.
In (nearly) all multiple user sessions combinations that I tested, both the SystemConfiguration and the stat solution return the same value.
It is worth pointing out, that when you have two users with graphical sessions at the same time, neither of the two solutions will actually return the “currently” active user session, but the user owning the sessions that was last logged in to. This is especially relevant when someone use Screen Sharing to create a remote graphical session on an Mac. In this case both methods will return the last user that logged in, even though both users currently have active sessions.
How the stat solution works
The stat
command shows the user owning the /dev/console
file. When we look at that ‘file’ with the ls
command:
> ls -al /dev/console
crw------- 1 armin staff /dev/console
We get a file type (the first character of the listing) of ‘c
’ which represents a ‘character special file.’ All the ‘files’ in /dev
are of this file type. When you look closely you will ‘files’ in here representing the disks connected to your Mac (disk*
), the Bluetooth devices (cu.*
), and some other weird ‘files.’
This stems back to the Unix underpinnings of macOS where everything has a representation in the file system hierarchy, whether it is actual data on a disk or not. The ‘files’ we are seeing here are an abstraction of devices and other objects in the system.
With that background in mind, it should be clear that we are looking at the same data from the system, abstracted in different ways. Either through the SystemConfiguration framework or the Unix file system. So, we get the same data either way, right?
The difference at login window
Not quite. I found one difference. When the Mac is at the login window, the SystemConfiguration solution returns an empty string and the stat solution returns root
.
This is not the entire truth, though. When you look closely at the SystemConfiguration solution, you will that there is a clause, in both the python and scutil versions, that catches a return value of loginwindow
and returns and empty string instead.
When you look at the raw output of the scutil
command used, we actually get quite a lot of information:
> scutil <<< "show State:/Users/ConsoleUser"
<dictionary> {
GID : 20
Name : armin
SessionInfo : <array> {
0 : <dictionary> {
kCGSSessionAuditIDKey : 100009
kCGSSessionGroupIDKey : 20
kCGSSessionIDKey : 257
kCGSSessionLoginwindowSafeLogin : FALSE
kCGSSessionOnConsoleKey : TRUE
kCGSSessionSystemSafeBoot : FALSE
kCGSSessionUserIDKey : 501
kCGSSessionUserNameKey : armin
kCGSessionLoginDoneKey : TRUE
kCGSessionLongUserNameKey : Armin Briegel
kSCSecuritySessionID : 100009
}
}
UID : 501
}
The awk
statement after the scutil
command filters out the information we need:
awk '/Name :/ && ! /loginwindow/ { print $3 }'
This will return lines containing Name :
and not containing loginwindow
. In other words, if the scutil
command returns loginwindow
in the line, the entire scutil ... | awk ...
construct will return nothing or an empty string.
When you leave out the extra awk
clause to filter the loginwindow
return value, you will get loginwindow
while the login window is shown:
scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ { print $3 }'
The edge of the edge case
So, the SystemConfiguration solution returns loginwindow
or nothing when the Mac is sitting at a login window and the stat solution returns root
. Not a big deal right? I just have to consider this difference when I test whether a user is logged in?
I found one edge case (to the edge case) where this difference matters: the stat
solution will return root
when at the login window and when the root account or System Administrator is logged in.
Now, enabling the root account and logging in and working as root is most definitely not recommended on macOS. So, there is an argument that this edge case should not occur.
But users have a way of ignoring these kind of recommendations. The SystemConfiguration solution will allow your scripts to actually distinguish between a Mac sitting at the login window and the (admittedly rare but extremely dangerous) situation where a user is logged in as root.
Conclusions
So, in most cases the stat solution is identical to the SystemConfiguration solution. It has the advantage of being shorter and easier to read.
When you anticipate that you might need to distinguish between the computer being at the login window and a root user being logged in, or when you are just paranoid about catching all edge cases, the SystemConfiguration solution is more complete.
If in doubt, use the SystemConfiguration solution.
What about this?
“`
who | awk ‘/console/{print $1}’
“`
That construct will return all users that currently have a console. It also will not detect if the Mac is sitting at the loginwindow when a user is logged in in the background.
Now, depending on your use case, that may be sufficient (i.e. you don’t allow fast user switching and remote screen sharing), or even better (i.e. you want to know all the logged in user names). I cannot make that judgement for you.
Howdy, I just ran into an undesirable result with the new method recommended for use with sh.
loggedInUser=$( echo “show State:/Users/ConsoleUser” | scutil | awk ‘/Name :/ && ! /loginwindow/ { print $3 }’ )
Using a Jamf Pro script I had a Catalina client return both the logged in user as well as “Setup User” when the policy ran at the end of enrollment.
Is there any way to prevent the return of that username alongside the actual logged in user?
I have not seen the command return _two_ values. But I have had situations where I would check for the value of `_mbsetupuser` before proceeding.
I’ve found an issue with the new “show state” method of pulling the username. It prefers user aliases over the originally created username. This will break scripts that need the original username for modifying filevault settings, for example.
You can reproduce it easily:
-create a user account
-add an alias for the user
-run the new method of obtaining the username
-log in with the new alias username
Though it’s unlikely any user would be doing this, it is the effect of modified user accounts with IDP sync solutions such as Jamf Connect. If the username differs between macOS and the IDP, an alias is created to allow login with either. The user will be logging in with the alias from that point forward, which introduces a complication in using this variable.
I’d love to hear your thoughts.
The age old command is: whoami. It’s really that simple.
No it is not. When the script is running as root from an installation script or management system, whoami will return ‘root’ and not the currently logged in user.