如何检查是否运行在一个bash脚本根

我正在编写一个需要根级权限的脚本,我想让它这样做,如果脚本不是以根身份运行,它只是回显“请以根身份运行”并退出。

下面是我正在寻找的一些伪代码:

if (whoami != root)
then echo "Please run as root"


else (do stuff)
fi


exit

我怎样才能最好地(干净和安全地)完成这个任务呢?谢谢!

啊,只是澄清一下:(做东西)部分将涉及运行本身需要root的命令。因此,作为一个普通用户运行它只会出现一个错误。这只是为了干净地运行一个需要根命令的脚本,而不需要在脚本中使用sudo,我只是在寻找一些语法糖。

309244 次浏览

如果脚本确实需要根访问权限,那么它的文件权限应该反映这一点。非根用户可执行根脚本将是一个危险信号。我建议你不要用if检查来控制访问。

chown root:root script.sh
chmod u=rwx,go=r script.sh

有一个简单的检查用户是否为root

# Fancy red-colored `error` function with `stderr` redirection with `exit`.
error ()
{
{ printf '\E[31m'; echo "$@"; printf '\E[0m'; } >&2
exit 1
}


# Test for root user.
if [ "$EUID" -eq 0 ]; then
error "Do not run this as the root user"
fi

这还假设如果失败,您希望以1退出。error函数是一种将输出文本设置为红色的天赋(不需要,但如果你问我,我觉得很漂亮)。

$EUID环境变量保存当前用户的UID。Root的UID为0。在你的脚本中使用这样的东西:

if [ "$EUID" -ne 0 ]
then echo "Please run as root"
exit
fi

如果你得到2: [: Illegal number:,检查顶部是否有#!/bin/sh,并将其更改为#!/bin/bash

正如@ wrkken在他的评论中提到的,id -u是一个更好的根检查。

此外,通过正确使用sudo,你可以检查脚本,看看它是否以根用户身份运行。如果不是,让它通过sudo召回自己,然后以根权限运行。

根据脚本的功能,另一个选项可能是为脚本可能需要的任何专门命令设置sudo项。

id -uwhoami好得多,因为像安卓这样的一些系统可能不提供根这个词。


示例命令:

# whoami

示例输出:

whoami: unknown uid 0

我的小笑话:

if [ "$(id -u)" -ne 0 ]; then echo "Please run as root." >&2; exit 1; fi

试试下面的代码:

#!/bin/sh


if [ "$(id -u)" -ne 0 ]; then
echo "Sorry, you are not root." >&2
exit 1
fi

编辑时注意:反勾号现在被认为已弃用(许多其他注释)。

在bash脚本中,有几种方法可以检查正在运行的用户是否是root用户。

作为警告,不要通过使用root用户名来检查用户是否为root。没有什么能保证ID为0的用户被称为root。这是一个被广泛遵循的强大约定,但任何人都可以为超级用户重命名其他名称。

我认为使用bash的最佳方法是使用手册页中的$EUID:

EUID   Expands to the effective user ID of the current  user,  initialized
at shell startup.  This variable is readonly.

这是一种比$UID更好的方法,$UID可以被更改,并且不能反映运行脚本的真实用户。

if (( $EUID != 0 )); then
echo "Please run as root"
exit
fi

我解决这类问题的一种方法是在我的命令中注入sudo,当不是以root身份运行时。这里有一个例子:

SUDO=''
if (( $EUID != 0 )); then
SUDO='sudo'
fi
$SUDO a_command

这样,当使用超级用户时,我的命令由root运行,当由普通用户运行时,由sudo运行。

如果您的脚本总是由根用户运行,只需相应地设置权限(0500)。

检查你是否是root用户,如果不是就退出:

if ((EUID != 0)); then
echo "Root or Sudo  Required for script ( $(basename $0) )"
exit
fi

或者在本例中,尝试在根位置创建目录,然后在提升权限后再尝试。

检查你是否是root,如果可能的话,如果不是elevate:

# Fails to create these dirs (needs sudo)
mkdir /test-dir-$(basename $0)
rmdir /test-dir-$(basename $0)


if ((EUID != 0)); then
echo "Granting root privileges for script ( $(basename $0) )"
if [[ -t 1 ]]; then
sudo "$0" "$@"
else
exec 1> output_file
gksu "$0" "$@"
fi
exit
fi
echo "Root privileges granted..."
# Creates Dirs as it now has rights
mkdir /test-dir-$(basename $0)
rmdir /test-dir-$(basename $0)

很简单的方法:

if [ "$(whoami)" = "root" ]; then
# you are root
else
# you are not root
fi

使用this而不是id的好处是,你可以检查某个非root用户是否也在运行该命令;如。

if [ "$(whoami)" = "john" ]; then
# you are john
else
# you are not john
fi

编者注:要么更喜欢使用posix兼容的测试命令和比较[... = ...],要么可以使用Bash/+其他特定于[[... == ...]]的shell。

已经给出了一些答案,但似乎最好的方法是使用:

  • id -u
  • 如果以root身份运行,将返回id为0。

这似乎比其他方法更可靠,即使脚本通过sudo运行,它似乎也会返回一个id为0。

让脚本只能由根用户运行的一个简单方法是用工作开始脚本:

#!/bin/su root

1 -阅读官方的GNU Linux文档,有很多正确的方法。

2 -确保你使用了正确的shell签名(工作)来避免解释错误:

#!/bin/bash

3 -最后,这是我的脚本根用户检查,运行在Bash:

#!/bin/bash


if [ "$EUID" -ne 0 ]; then
echo "Please run as root/with sudo." >&2
exit 1
else
:    # do your stuff as root user
fi

在Bash中,我的观点是:

#!/bin/bash


# Display the UID variable
echo "Your UID is $UID"


# Check for root user via UID
if [ "$UID" -eq 0 ]; then
echo "You are root"
else
echo "You are not root user" >&2
fi

我的检查root用户,运行在Bash:

#!/bin/bash


if [ "$UID" -eq 0 ]; then
echo "You are root."
else
echo "You are just an ordinary user." >&2
fi

据我所知,正确的检查方法如下:

#!/bin/sh


if [ "$(id -u)" -eq 0 ]; then
echo "You are root."
else
echo "You are NOT root." >&2
fi

OP注:你可能会看到“根测试”;部分

编者注:我稍微调整了这个shell片段,使其与posix兼容。

在这个回答中,让它更清楚,我假设读者能够理解bashzsh现代外壳与dash等便携式POSIX外壳之间的区别。

我相信这里没有太多要解释的,因为高投票的答案已经很好地解释了大部分问题。

但是,如果有什么需要进一步解释的,请不要犹豫,我会尽我最大的努力填补空白。


优化的性能和可靠性全方位解决方案;所有外壳兼容

新的解决方案:

# bool function to test if the user is root or not
is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; }

基准测试(保存到文件is_user_root__benchmark)

#+------------------------------------------------------------------------------+
#|                           is_user_root() benchmark                           |
#|                  "Bash is fast while Dash is slow in this"                   |
#|                          Language: POSIX shell script                        |
#|                     Copyright: 2020-2021 Vlastimil Burian                    |
#|                      M@il: info[..]vlastimilburian[..]cz                     |
#|                               License: GPL 3.0                               |
#|                                 Version: 1.2                                 |
#+------------------------------------------------------------------------------+


readonly iterations=10000


# intentionally, the file does not have an executable bit, nor it has a shebang
# to use it, just call the file directly with your shell interpreter like:


# bash is_user_root__benchmark    ## should take a fraction of one second
# dash is_user_root__benchmark    ## could take around 10 seconds


is_user_root () { [ "${EUID:-$(id -u)}" -eq 0 ]; }


print_time   () { date +"%T.%2N"; }
print_start  () { printf '%s' 'Start  : '; print_time; }
print_finish () { printf '%s' 'Finish : '; print_time; }


printf '%s\n' '___is_user_root()___'; print_start
                   

i=1; while [ "$i" -lt "$iterations" ]; do
is_user_root
i=$((i+1))
done; print_finish

使用和持续时间的例子:

$ dash is_user_root__benchmark
___is_user_root()___
Start  : 03:14:04.81
Finish : 03:14:13.29


$ bash is_user_root__benchmark
___is_user_root()___
Start  : 03:16:22.90
Finish : 03:16:23.08

解释

由于读取$EUID标准bash变量(有效的用户ID号)要比执行id -u命令POSIX-ly查找用户ID快许多倍,因此这个解决方案将两者结合成一个包装良好的函数。当且仅当$EUID由于任何原因不可用时,id -u命令将被执行,确保我们得到正确的返回值无论情况如何


为什么我在OP问了这么多年之后才发布这个解决方案

好吧,如果我没看错的话,上面似乎确实少了一段代码。

你看,有很多变量必须考虑在内,其中一个是结合性能和可靠性


便携式 POSIX解决方案+上述函数的使用示例

#!/bin/sh


# bool function to test if the user is root or not (POSIX only)
is_user_root ()
{
[ "$(id -u)" -eq 0 ]
}


if is_user_root; then
echo 'You are the almighty root!'
# You can do whatever you need...
else
echo 'You are just an ordinary user.' >&2
exit 1
fi

结论

尽管您可能不喜欢它,但Unix / Linux环境已经变得非常多样化。这意味着有些人非常喜欢bashzsh和其他现代shell,他们甚至没有想到可移植性(POSIX)。如今,这是个人选择和需要的问题。

使用:id -u$EUIDwhoami的问题是,当我伪造根时,它们都给出了假阳性,就像这样:

$ fakeroot

id:

$ id -u
0

$EUID:

$ echo $EUID
0

whoami:

$ whoami
root

那么一个可靠的(和黑客)方法是验证用户是否有权访问/root目录:

$ ls /root >/dev/null 2>&1 && is_root=true || is_root=false; echo "$is_root"

我的小笑话:

[ "$(whoami)" != root ] && echo "Please, run as root." >&2 && exit 1

需要注意的是,无论何时使用sudo运行脚本,“用户上下文”或环境都会切换到root

但是特奥,这意味着什么?

好吧,我年轻的弟子,这意味着如果一个padawan用户使用sudo运行一个包含波浪号(~)的脚本,无论什么时候bash将展开~,结果将是/root而不是/home/<user>(即,在这种情况下/home/padawan),或者如果你创建一个目录或文件的所有者和组将是root,而不是在这种情况下执行脚本的padawan,因为用户环境被切换了。

例如,让我们检查这个脚本install-app.sh:

#!/bin/bash
ROOT_UID=0   # Only users with $UID 0 have root privileges.
E_NOTROOT=87 # Non-root exit error.


## Prevent the execution of the script if the user has no root privileges
if [ "${UID:-$(id -u)}" -ne "$ROOT_UID" ]; then
echo 'Error: root privileges are needed to run this script'
exit $E_NOTROOT
fi
...
mkdir -vp ~/app/init
touch config
...
touch /home/<user>/app/init/profile
service mysql start
...

如果我们使用sudo运行:

sudo install-app.sh

这将创建目录,config文件将如下所示:

##
## ~ (/root)
drwxr-xr-x 17 root root    4096 Nov 23 20:45 ./
drwxr-xr-x  5 root root    4096 Nov 15 19:04 ../
...
drwxr-xr-x  3 root root    4096 Nov 25 14:30 app/
...
drwxr-xr-x  2 root root    4096 Nov 16 19:08 tmp/


## ~/app (/root/app)
drwxr-xr-x  3 root root 4096 Nov 25 14:30 ./
drwxr-xr-x 17 root root 4096 Nov 25 14:33 ../
drwxr-xr-x  2 root root 4096 Nov 25 14:33 init/


## ~/app/init (/root/app/init)
drwxr-xr-x 2 root root 4096 Nov 25 14:33 ./
drwxr-xr-x 3 root root 4096 Nov 25 14:30 ../
-rw-r--r-- 1 root root    0 Nov 25 14:33 config


## /home/<user>/app/conf
drwxr-xr-x 2 <user> <user> 4096 Nov 25 14:43 ./
drwxr-xr-x 3 <user> <user> 4096 Nov 25 14:30 ../
-rw-r--r-- 1 root   root      0 Nov 25 14:43 profile

正如你所知,剧本简直一团糟。现在<user>不能访问profile文件,没有sudo也不能修改config。一开始可能是不重要的,但相信我,如果你的项目变大了,有人会运行脚本,弄乱你的系统。

建议

我的建议是:请求用户验证是否是sudoer。然后,将sudo添加到需要它的命令中。

将这些更改应用到脚本将如下所示:

#!/bin/bash
E_NOTROOT=87 # Non-root exit error.


## Prevent the execution of the script if the user has no root privileges
## Check if is sudoer
if ! $(sudo -l &>/dev/null); then
echo 'Error: root privileges are needed to run this script'
exit $E_NOTROOT
fi
...
mkdir -vp ~/app/init
touch config
...
touch /home/<user>/app/init/profile
sudo service mysql start
...

这个修改允许用户像这样运行脚本:

install-app.sh

用户将被要求插入他的密码,以验证是否是sudoer。之后,mkdir -vp ~/app/init将在用户家中创建文件:

/home/<user>/app/init
/home/<user>/app/init/config
/home/<user>/app/init/profile

此外,我建议获取用户的荷马目录并将其用作常量。

## Defines user home directory
USER_HOME_DIR=$(getent passwd ${SUDO_USER:-$USER} | cut -d: -f6)
...
mkdir -vp "$USER_HOME_DIR/app/init"
...

我无意中发现了同样的问题。#!/bin/su root将是一个优雅的方法,但在某些(更高)版本的bash上不起作用,正如@basickarl所提到的那样。此外,当您使用/bin/bash ./some_script.(b)sh启动脚本时,此方法可能会失败。

我发现关于SO的另一页中使用了环境变量SUDO_UIDSUDO_USER。当一个普通用户使用sudo调用一个脚本时,这些在脚本中可用。否则它们就是空的。另外:当你以根用户身份登录时,这些值也是空的(或未设置-不完全确定)。

所以这是我目前使用的解决方案:

isSudo()
{
local _fn="${FUNCNAME[0]}"
if [[ $(/usr/bin/id -u) -eq 0 ]] ; then
if [[ ! -z "$SUDO_UID" && ! -z "$SUDO_USER" ]] ; then
>2 echo -e "$_fn: invoked as sudo-user ($SUDO_USER)."
else
>2 echo -e "$_fn: invoked as root ($SUDO_USER)."
fi
return 0
fi
>2 echo "$_fn: not as root or as sudoer."
return 1
}


然后像这样使用它。

if $(isSudo) ; then
echo -e "Script started as root or as sudoer."
else
echo -e "Script not started as root or with sudo"
fi

在echo前面的>2是为了将echo重定向到stderr,否则if语句就会失控,这只是为了进行一点调试。除了使用echo,你可以save“;root”,“;sudoer”;或“;user"在局部变量中,并让函数回显,然后对其执行case,如下所示:

how_invoked()
{
local _invoked_as="USER"
if [[ $(/usr/bin/id -u) -eq 0 ]] ; then
if [[ ! -z "$SUDO_UID" && ! -z "$SUDO_USER" ]] ; then
_invoked_as="SUDOER"
else
_invoked_as="ROOT"
fi
fi
echo -n "$_invoked_as" # -n prevents newline
}

然后像这样使用它:

INVOKED_AS=$(how_invoked)
case $INVOKED_AS in
USER)
echo -e "Invoked as user." ;;
SUDOER)
echo -e "Invoked as sudoer." ;;
ROOT)
echo -e "Invoked as root." ;;
*)
echo -e "I'm confused" ;;
esac

或者如果你只是想测试其中一个:

if [ "$(how_invoked)" == "SUDOER" ] ; then
echo -e "You are a sudoer."
else
echo -e "You are NOT a sudoer."
fi

希望这能有所帮助。