【问题标题】:How to get $HOME directory when switching to a different user in bash?如何在 bash 脚本中获取不同用户的 $HOME 目录?
【发布时间】:2013-12-28 13:43:04
【问题描述】:

我需要以其他用户的身份在该用户的$HOME 目录中执行部分 bash 脚本。但是,我不确定如何确定这个变量。切换到该用户并调用 $HOME 未提供正确的位置:

# running script as root, but switching to a different user...
su - $different_user
echo $HOME
# returns /root/ but should be /home/myuser

更新:

看来问题出在我尝试在脚本中切换用户的方式上:

$different_user=deploy

# create user
useradd -m -s /bin/bash $different_user

echo "Current user: `whoami`"
# Current user: root

echo "Switching user to $different_user"
# Switching user to deploy

su - $different_user
echo "Current user: `whoami`"
# Current user: root
echo "Current user: `id`"
# Current user: uid=0(root) gid=0(root) groups=0(root)

sudo su $different_user
# Current user: root
# Current user: uid=0(root) gid=0(root) groups=0(root)

在 bash 脚本中切换用户和以不同用户身份执行命令的正确方法是什么?

【问题讨论】:

  • 你总是可以从 /etc/passwd 中提取它,假设它是一个本地定义的帐户。
  • su - $different_user 应该足够了。你确定你甚至登录为myuser。尝试运行id 命令进行验证。某些登录 shell 设置为 nologin,一旦您以 myuser 登录,它就会退出。
  • @anubhava 我更新了我的问题以包括我如何创建用户。本质上:useradd -m -s /bin/bash $different_user.
  • 请再看看我的回答。我对它进行了大幅更改以完全匹配您尝试做的事情。
  • 请不要将evalbash -c 与变量一起使用。我添加了一个适用于带有 bash 的 Linux/Unix/macOS 系统的答案(即使您没有使用 bash 作为 shell,它也可能有 bash 可用,因为 bashism 无处不在)。 stackoverflow.com/a/53219743/117471 也说明了其他答案的危险。

标签: bash


【解决方案1】:

这个问题的标题是How to get $HOME directory of different user of different user in bash script?,这就是人们从谷歌来到这里的目的。

有一种安全的方法可以做到这一点!

在没有 sudo 或 root 的 Linux/BSD/macOS/OSX 上

user=pi
user_home=$(bash -c "cd ~$(printf %q $USER) && pwd")

注意:之所以安全,是因为 bash(甚至是 4.4 之前的版本)有自己的 printf 函数,其中包括:

%q quote the argument in a way that can be reused as shell input

见:help printf

比较这里的其他答案如何响应代码注入

# "ls /" is not dangerous so you can try this on your machine
# But, it could just as easily be "sudo rm -rf /*"
$ user="root; ls /"
$ printf "%q" "$user"
root\;\ ls\ /

# This is what you get when you are PROTECTED from code injection
$ user_home=$(bash -c "cd ~$(printf "%q" "$user") && pwd"); echo $user_home
bash: line 0: cd: ~root; ls /: No such file or directory

# This is what you get when you ARE NOT PROTECTED from code injection
$ user_home=$(bash -c "cd ~$user && pwd"); echo $user_home
bin boot dev etc home lib lib64 media mnt ono opt proc root run sbin srv sys tmp usr var /root

$ user_home=$(eval "echo ~$user"); echo $user_home
/root bin boot dev etc home lib lib64 media mnt ono opt proc root run sbin srv sys tmp usr var

在 Linux/BSD/macOS/OSX 上作为 root

如果您这样做是因为您正在以 root 的身份运行某些东西,那么您可以使用 sudo 的强大功能:

user=pi
user_home=$(sudo -u $user sh -c 'echo $HOME')

在没有 sudo 或 root 的 Linux/BSD(但不是现代 macOS/OSX)上

如果没有,您可以从/etc/passwd 获取。使用evalgetent的例子已经很多了,我再给个选择:

user=pi
user_home=$(awk -v u="$user" -v FS=':' '$1==u {print $6}' /etc/passwd)

如果我有一个带有许多其他 awk oneliner 的 bash 脚本并且没有使用 cut,我真的只会使用那个。虽然许多人喜欢“编写高尔夫代码”以使用最少的字符来完成任务,但我更喜欢“工具高尔夫”,因为使用更少的工具可以让您的脚本具有更小的“兼容性足迹”。此外,您的同事或未来的自己需要阅读的手册页更少。

【讨论】:

    【解决方案2】:

    更新:根据这个问题的标题,人们似乎来到这里只是想找到不同用户的主目录,而不需要冒充该用户

    在这种情况下,最简单的解决方案是将 tilde expansion 与感兴趣的用户名结合使用,并结合 eval(这是必需的,因为用户名必须作为 不带引号的文字,以便波浪号扩展起作用):

    eval echo "~$different_user"    # prints $different_user's home dir.
    

    注意:通常caveats regarding the use of eval申请;在这种情况下,假设您控制了$different_user 的值并知道它只是一个用户名。

    相比之下,此答案的其余部分涉及模拟用户在该用户的主目录中执行操作强>。


    注意:

    • 默认管理员和通过sudoers 文件授权的其他用户可以通过sudo 模拟其他用户。
    • 以下内容基于sudo默认 配置 - 更改其配置可以使其行为不同 - 请参阅 man sudoers

    以其他用户身份执行命令的基本形式是:

    sudo -H -u someUser someExe [arg1 ...]
      # Example:
    sudo -H -u root env  # print the root user's environment
    

    注意:

    • 如果您忽略指定-H,模拟进程(在指定用户的上下文中调用的进程)将在$HOME 中报告原始用户的主目录。
    • 模拟进程将与调用进程具有相同的工作目录。
    • 模拟过程对作为参数传递的字符串文字执行无 shell 扩展,因为模拟过程中不涉及任何 shell(除非 someExe 恰好是一个 shell) - 由 invoking shell - 在传递给模拟进程之前 - 显然仍然可以发生。

    或者,您可以通过在someExe 前面加上-i -s 来让模拟进程作为或通过(n 模拟)shell 运行 - 不指定 someExe ... 创建一个 interactive shell:

    • -isomeUser 创建一个login shell,这意味着以下内容:

      • someUser用户特定的 shell 配置文件(如果已定义)已加载
      • $HOME 指向someUser 的主目录,因此不需要-H(尽管您仍然可以指定它)
      • 模拟 shell 的工作目录是 someUser 的主目录。
    • -s 创建一个非登录 shell:

      • 没有加载 shell profile(尽管交互式 nonlogin shell 的初始化文件是;例如,~/.bashrc
      • 除非您还指定-H,否则模拟过程将报告$HOME 中的原始用户主目录。
      • 模拟 shell 将与调用进程具有相同的工作目录。

    使用 shell 意味着命令行上传递的字符串参数可能受 shell 扩展的影响 - 请参阅下面的平台特定差异 - 由模拟 shell(可能在调用外壳的初始扩展);比较以下两个命令(使用 single 引号来防止 invoking shell 过早扩展):

      # Run root's shell profile, change to root's home dir.
    sudo -u root -i eval 'echo $SHELL - $USER - $HOME - $PWD'
      # Don't run root's shell profile, use current working dir.
      # Note the required -H to define $HOME as root`s home dir.
    sudo -u root -H -s eval 'echo $SHELL - $USER - $HOME - $PWD'
    

    调用什么 shell 由“设置的 SHELL 环境变量或 passwd(5) 中指定的 shell”确定(根据 man sudo)。请注意,对于-s,重要的是调用用户的环境,而对于-i,它是模拟用户的环境。

    请注意,与 shell 相关的行为存在平台差异(-i-s

    • sudo 在 Linux 上显然只接受 executable 或 builtin 名称作为 -s/-i 之后的 first 参数,而 OSX 允许传递整个 shell 命令行;例如,OSX 直接接受sudo -u root -s 'echo $SHELL - $USER - $HOME - $PWD'(不需要eval),而Linux 不接受(截至sudo 1.8.95p)。

    • Linux 上的旧版本 sudo 不会将 shell 扩展应用于传递给 shell 的参数;例如,对于sudo 1.8.3p1(例如,Ubuntu 12.04),sudo -u root -H -s echo '$HOME' 只是回显字符串文字“$HOME”,而不是在 root 用户的上下文中扩展变量引用。至少从 sudo 1.8.9p5(例如 Ubuntu 14.04)开始,此问题已得到修复。因此,为了确保即使使用较旧的sudo 版本也能在 Linux 上进行扩展,请将整个命令作为单个参数传递给 eval;例如:sudo -u root -H -s eval 'echo $HOME'。 (虽然在 OSX 上不是必需的,但这也可以在那里工作。)

    • root 用户的$SHELL 变量在 OSX 10.9 上包含 /bin/sh,而在 Ubuntu 12.04 上是 /bin/bash

    无论模拟过程是否涉及shell,其环境都会设置以下变量,反映调用用户和命令:SUDO_COMMANDSUDO_USERSUDO_UID=SUDO_GID

    更多细节请参见man sudoman sudoers

    向@DavidW 和@Andrew 致敬,寻求灵感。

    【讨论】:

      【解决方案3】:

      getent passwd username 的输出可以用 Bash 正则表达式解析

      OTHER_HOME="$(
        [[ "$(
          getent \
          passwd \
          "${OTHER_USER}"
        )" =~ ([^:]*:){5}([^:]+) ]] \
        && echo "${BASH_REMATCH[2]}"
      )"
      

      【讨论】:

        【解决方案4】:

        如果用户不存在,getent 将返回错误。

        这是一个不会忽略getent退出代码的小shell函数:

        get_home() {
          local result; result="$(getent passwd "$1")" || return
          echo $result | cut -d : -f 6
        }
        

        这是一个用法示例:

        da_home="$(get_home missing_user)" || {
          echo 'User does NOT exist!'; exit 1
        }
        
        # Now do something with $da_home
        echo "Home directory is: '$da_home'"
        

        【讨论】:

          【解决方案5】:

          我一直在努力解决这个问题,因为我正在寻找一种在 OS X 的 bash 脚本中执行此操作的方法,因此 /etc/passwd 是不可能的,我的脚本应该以 root 身份执行,因此使调用 eval 或 bash -c 的解决方案很危险,因为它们允许将代码注入到指定用户名的变量中。

          这是我发现的。它很简单,并且不会在子外壳中放置变量。但是,它确实需要由 root 运行脚本,因为它需要 sudo 到指定的用户帐户。

          假设 $SOMEUSER 包含一个有效的用户名:

          echo "$(sudo -H -u "$SOMEUSER" -s -- "cd ~ && pwd")"
          

          我希望这对某人有帮助!

          【讨论】:

            【解决方案6】:

            这适用于 Linux。不确定它在其他 *nixes 中的表现如何。

              getent passwd "${OTHER_USER}"|cut -d\: -f 6
            

            【讨论】:

              【解决方案7】:

              又快又脏,并将其存储在变量中:

              USER=somebody
              USER_HOME="$(echo -n $(bash -c "cd ~${USER} && pwd"))"
              

              【讨论】:

                【解决方案8】:

                对于那些正在寻找一种轻量级方法来查找用户主目录的人来说,这是一个替代答案...

                而不是搞乱su hacks,或者烦恼启动另一个bash shell 只是为了找到$HOME 环境变量...

                通过 Bash 进行轻量级简单 Homedir 查询

                有一个专门用于此的命令:getent

                getent passwd someuser | cut -f6 -d:

                getent 可以做的更多...看看man pagepasswd nsswitch 数据库将以/etc/passwd 格式返回用户条目。只需将其拆分为冒号: 即可解析出字段。

                它应该安装在大多数 Linux 系统(或任何使用 GNU Lib C(RHEL:glibc-common,Deb:libc-bin)的系统上

                【讨论】:

                • getent 正如您所说,特定于Linux;一种更简单且与平台无关的方法是将 bash 的tilde expansion 与感兴趣的用户名一起使用(需要eval,因为用户名必须作为不带引号的文字 才能使扩展工作) :eval echo "~$different_user" - 重复使用eval 的常见警告适用。
                • 让 bash 来做是个好主意!当前用户主页的另一种选择是:eval echo "~$(who -m | awk '{ print $1 }')" 或简单的eval echo "~"。在 Mac OS X 上,有 dscl 实用程序,但它的输出更难解析,并且要求您已经传入 /Users homedir 或 uid(例如:dscl . -read /Users/$(who -m | awk '{ print $1 }')dscl . -search /Users UniqueID $(id -u))。在某些平台上使用 bash 可能更容易,或者无论如何进行某种基本的操作系统检测。
                • 对于当前用户,您根本不需要eval:未引用的~ 将返回当前用户的主目录(例如echo ~)。如果您确实需要当前用户的用户名,$USER 是最简单、高效且符合 POSIX 的; whoami 也很简单,但相对昂贵且非 POSIX。您的基于who -m 的命令虽然符合POSIX,但成本更高。总结:是的,使用与 POSIX 兼容的 shell(例如 Bash)是最简单、最高效且可移植的。
                • getent 特定于 Linux,但为什么呢?拥有(基本上是一个查询数据库的命令行界面,尽管是通过文本文件实现的)似乎是一件明显有用的东西,并且应该易于移植。好吧,它还应该有一种从记录中提取值的方法。这将避免使用cut
                • @DavidTonhofer:getent 而不是仅仅从/etc/passwd 读取的原因是因为可能存在不同来源的用户和登录身份验证数据后端。用户数据库有多种后端,例如sssd (sss/LDAP)、NIS 和 NIS+。有关这方面的更多信息,请参阅this SE Answer about nsswitch.conf。额外削减的需求并不是我同意的最大需求,但它是工具输出的。使用getent的真正原因是尝试查询系统上配置的用户数据库后端。
                【解决方案9】:

                我也在找这个,但不想冒充用户来获取路径!

                user_path=$(grep $username /etc/passwd|cut -f6 -d":");

                现在在您的脚本中,您可以引用$user_path,大多数情况下是/home/username

                假设:您之前已将 $username 设置为预期用户用户名的值。 来源:http://www.unix.com/shell-programming-and-scripting/171782-cut-fields-etc-passwd-file-into-variables.html

                【讨论】:

                • passwd 文件只包含本地用户,因此这不适用于 AD/LDAP 集成用户。你应该改用'getent'提供的抽象层(见上面的例子)
                【解决方案10】:

                所以你想:

                1. 以其他用户身份执行 bash 脚本的一部分
                2. 切换到该用户的 $HOME 目录

                this answer 的启发,这是您脚本的改编版本:

                #!/usr/bin/env bash
                
                different_user=deploy
                
                useradd -m -s /bin/bash "$different_user"
                
                echo "Current user: $(whoami)"
                echo "Current directory: $(pwd)"
                echo
                
                echo "Switching user to $different_user"
                sudo -u "$different_user" -i /bin/bash - <<-'EOF'
                    echo "Current user: $(id)"
                    echo "Current directory: $(pwd)"
                EOF
                echo
                
                echo "Switched back to $(whoami)"
                
                different_user_home="$(eval echo ~"$different_user")"
                echo "$different_user home directory: $different_user_home"
                

                当你运行它时,你应该得到以下信息:

                Current user: root
                Current directory: /root
                
                Switching user to deploy
                Current user: uid=1003(deploy) gid=1003(deploy) groups=1003(deploy)
                Current directory: /home/deploy
                
                Switched back to root
                deploy home directory: /home/deploy
                

                【讨论】:

                • 你应该使用#!/usr/bin/env bash作为shebang。
                【解决方案11】:

                在 BASH 中,您可以通过在用户的登录 ID 前加上波浪号来查找用户的 $HOME 目录。例如:

                $ echo ~bob
                

                这将回显用户bob$HOME 目录。

                但是,您说您希望能够以特定用户的身份执行脚本。为此,您需要设置sudo。此命令允许您以特定用户的身份执行特定命令。比如以bob用户身份执行foo

                $ sudo -i -ubob -sfoo
                

                这将启动一个新的 shell,-i 将使用用户的默认环境和 shell 模拟登录(这意味着 foo 命令将从 bob's$HOME` 目录执行。)

                Sudo 设置起来有点复杂,您需要成为超级用户才能看到 shudders 文件(通常是 /etc/sudoers)。但是,该文件通常有几个示例可供您使用。

                在此文件中,您可以指定您指定谁可以运行命令、作为哪个用户以及该用户是否必须在执行该命令之前输入其密码的命令。这通常是默认设置(因为它证明这是用户,而不是在用户获得可乐时经过的人。)但是,当您运行 shell 脚本时,您通常希望禁用此功能。

                【讨论】:

                • 很有希望,但 -i-s 似乎是相互排斥的。只需-i 就足够了。
                • 无法将~$different_user 分配给变量并将其转换为$HOME。你能举个例子吗?
                • @Andrew 不要使用引号! user_home=~$different_user。请注意,~$different_user 周围没有引号。您的操作系统也可能会阻止您重新分配 HOME,因为它确实具有特殊含义。你在用sudo吗?这是推荐的方式。
                【解决方案12】:

                在这种情况下,您需要 sudo-u 选项。来自man 页面:

                The -u (user) option causes sudo to run the specified command as a user other than root.
                

                如果您不需要像他们一样实际运行它,您可以使用~&lt;user&gt; 移动到他们的主目录。例如,要进入我的主目录,您可以使用 cd ~chooban

                【讨论】:

                • 你的意思是:sudo -u $different_user su -?
                • @Andrew 不!不要su 作为其他用户。使用您要以该用户身份运行的命令。例如,sudo -u $different_user $command
                • @DavidW 我想我想找到一种方法来运行多个命令
                • 如果需要,您可以在单个脚本中使用sudo 运行多个命令。使用 sudo 并设置它是最好的方法。您的命令可以在没有提示的情况下以该用户身份运行。否则,您必须以该用户身份登录,这将启动一个新的 shell,这意味着您必须手动运行命令。您想自动执行此操作。
                猜你喜欢
                • 2022-06-16
                • 2010-09-08
                • 1970-01-01
                • 2014-06-30
                • 2020-09-06
                相关资源
                最近更新 更多