Reputation: 1542
file name test.sh
echo $HOME
running in root privilege -> sudo test.sh
expected
/home/username/
but getting
/root
Upvotes: 9
Views: 9895
Reputation: 4297
I had a need to know the true login of the user and realised that no method I could find was universal. So I wrote a script. Decided to post it here as this question (out of the dozens of identical ones) was where I gave up searching.
This does illustrate a point that the only semi reliable method I am aware of is the shell expansion of ~
. And even that won't work if there is no shell to expand ~
into.
Only kidding :-) not 'wrong' but not universal. Maybe this gets a little closer but I expect it's also not 100% universal, probably far less.
Some people have suggested ${SUDO_USER}
. That is not a universal and portable solution, firstly because su
exists and secondly because a user may already have used "sudo", see the example below. Others have suggested $(logname)
, that also breaks in the same way. (So I expect there is a bug in the libc API *)
* The man page for logname(1) points to libc(7) function getlogin(3), which admits it's rubbish.
So I would say this answer is an answer but it really depends on what you need. It does contain some nice output formatting though :o)
sudo -i
sudo -i
echo "${SUDO_USER}"
# will print "root"
echo "$(logname)"
# will also print "root"
echo "~"
# prints "/root"
echo "~user"
# prints "/home/user"
In researching this I found that unless you know the username before hand there is no easy way to find the true login username and thus the home directory of the current user once they sudo more than once.
Ieeded this on order to provide a script that a user would use to configure their environment when installing a WSL image. But that script would have to run sudo.
Without this, if they did more than one sudo it would break in a very bad way. I've noticed people copy-paste recipes and often forget to remove the sudo when they should.
As a resolution to this problem I wrote this script as hopefully a slightly more reliable means of determining the true login username and hence home folder regardless of how that is defined, from passwd/NIS/LDAP/PAM whatever. The source code for this is in a gist on github and under MIT license.
I would not be surprised if there are still edge cases where this also won't work.
Follow the process tree back iteratively, process->parent, Pid->PPid, until we get to login
or fall off the end with a Pid=1 (usually called init
).
For a given user, the next thing run after login
will be run by the UID of that logged in user.
This is usually a shell; e.g. bash, ash, fish, sh, dash, tch; but also could be some sort of non-interactive login using ssh, rsh, rexec or similar.
I think this method will also work in a case where the ~
will not work, i.e. when there is no shell run after login and login just runs a command. Any shell run after that by some program would lack the immediate parent login
process so no source of which user was behind it as the environment may not get passed on.
If this script runs it will just get to process 1 - which is always run by root (or boot), how do we detect that it's the user?
UID=0
if that exists or the current UID
. So that this will also work if the user logs in as root at the beginning./proc/<PID>/environ
to find the ${HOME}
variable as that is set by login. This could also be used to detect the login as before than HOME is meaningless.NOTE: You will notice that the login
program is not called login here as this was run on WSL.
I think this would currently break if a root login user sudos to a real user then (assuming that real user has sudo privelages) sudos back. So it would then choose the wrong user, but that would be easy to avoid as we know the initial process after login. It may also break when tmux or screen are used. (TODO: test that)
#!/bin/bash
# get_real_login.sh
# Get the name of the REAL user who is running this script
VERBOSE=1
# Do it lots, use a function
function val () {
echo "$(cat < /dev/stdin)"|tr -d ' \t'|cut -f2 -d:
}
# $$ is the the PID of this script
CPID=$$
# Current user (obviously root)
REAL_UID=$(id -u)
# Fallback HOME
REAL_HOME=/
while [ "${CPID}" -ne 1 ]; do {
if [ "${CPID}" -ne 1 ]; then
STAT=$(cat /proc/${CPID}/status)
CPID_P=$(echo "${STAT}"|grep PPid|val)
CPN=$(echo "${STAT}"|grep Name|val)
CPUID=$(echo "${STAT}"|grep Uid)
CPUID=$(($(echo ${CPUID}|tr -s ' \t' '::'|val)))
CPUN=$(id -un ${CPUID})
ENV_HOME=$(cat /proc/${CPID}/environ|tr '\0' '\n'|grep HOME|cut -d= -f2)
test -n "${VERBOSE}" && {
echo "CMD:${CPN}";
echo "PID:${CPID}";
echo "PPID:${CPID_P}";
echo "UID:${CPUID}";
echo "USER:${CPUN}";
} | printf "%-20.20s %-10.10s %-10.10s %-10.10s %-15.15s\n" $(cat < /dev/stdin)
if [ ${CPUID} -ne 0 ]; then
REAL_UID=${CPUID}
fi
test -n "${ENV_HOME}" && REAL_HOME="${ENV_HOME}"
CPID="${CPID_P}"
fi
}; done
REAL_USERNAME=$(id -un ${REAL_UID})
EXISTS=$(test -d $(readlink ${REAL_HOME}) && \
echo " - EXISTS" || echo " - MISSING !!")
echo -e "\nLogin User: ${REAL_USERNAME} (UID:${REAL_UID}) \${HOME}=\"${REAL_HOME}\"${EXISTS}\n"
Before running this I had done a couple sudo -i
commands to hide my real login.
Interestingly this trace shows sudo creates two processes per sudo, not one!!
root@WSL:~# ./get_real_login.sh
CMD:get_real_lo PID:31743 PPID:10329 UID:0 USER:root
CMD:bash PID:10329 PPID:10328 UID:0 USER:root
CMD:sudo PID:10328 PPID:10320 UID:0 USER:root
CMD:sudo PID:10320 PPID:10174 UID:0 USER:root
CMD:bash PID:10174 PPID:10173 UID:0 USER:root
CMD:sudo PID:10173 PPID:10165 UID:1000 USER:user1000
CMD:sudo PID:10165 PPID:258 UID:1000 USER:user1000
CMD:bash PID:258 PPID:255 UID:1000 USER:user1000
CMD:Relay(258) PID:255 PPID:253 UID:0 USER:root
CMD:SessionLead PID:253 PPID:2 UID:0 USER:root
CMD:init-system PID:2 PPID:1 UID:0 USER:root
Login User: user1000 (UID:1000) ${HOME}="/home/user1000" - EXISTS
Upvotes: 2
Reputation: 3440
Surely the best way is simply:
echo ~username
For assignment, tilde must not be quoted:
username_home=~username
but may be combined with quoted strings, e.g.:
new_path=~username/"${another_variable}"
new_paths_array=(~username/"${yet_another}"/*)
There may be some subtle things to do with $HOME
and ~
, especially in relation to sudo
.
Refer to the POSIX standard regarding tilde expansion. For bash, also see bash tilde expansion.
Upvotes: 1
Reputation: 1031
sudo
runs the script as the root-user
To get the name of the user who initiated sudo you can call echo $SUDO_USER
To get its home directory:
getent passwd $SUDO_USER | cut -d: -f6
Upvotes: 11