Alen Paul Varghese
Alen Paul Varghese

Reputation: 1542

How to get $HOME directory of user in bash script root mode?

file name test.sh

echo $HOME

running in root privilege -> sudo test.sh

expected

/home/username/

but getting

/root

Upvotes: 9

Views: 9895

Answers (3)

Jay M
Jay M

Reputation: 4297

Why?

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.

Improvement?

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.

Others are WRONG!

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)

First, illustrate the problem:

sudo -i
sudo -i
echo "${SUDO_USER}"
# will print "root"
echo "$(logname)"
# will also print "root"
echo "~"
# prints "/root"
echo "~user"
# prints "/home/user"

Why I needed better:

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.

Solution:

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.

Method:

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 loginprocess so no source of which user was behind it as the environment may not get passed on.

Final Selection:

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?

  • We will select the last found non 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.
  • We will take the last valid /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.

Edge cases?

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!!

Output (with VERBOSE=1)

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

Jack Wasey
Jack Wasey

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

ToTheMax
ToTheMax

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

Related Questions