通过脚本检查根完整性
Checking root integrity via a script
下面是我检查根路径完整性的脚本,以确保 PATH 变量中没有漏洞。
#! /bin/bash
if [ ""`echo $PATH | /bin/grep :: `"" != """" ]; then
echo "Empty Directory in PATH (::)"
fi
if [ ""`echo $PATH | /bin/grep :$`"" != """" ]; then echo ""Trailing : in PATH""
fi
p=`echo $PATH | /bin/sed -e 's/::/:/' -e 's/:$//' -e 's/:/ /g'`
set -- $p
while [ """" != """" ]; do
if [ """" = ""."" ]; then
echo ""PATH contains ."" shift
continue
fi
if [ -d ]; then
dirperm=`/bin/ls -ldH | /bin/cut -f1 -d"" ""`
if [ `echo $dirperm | /bin/cut -c6 ` != ""-"" ]; then
echo ""Group Write permission set on directory ""
fi
if [ `echo $dirperm | /bin/cut -c9 ` != ""-"" ]; then
echo ""Other Write permission set on directory ""
fi
dirown=`ls -ldH | awk '{print }'`
if [ ""$dirown"" != ""root"" ] ; then
echo is not owned by root
fi
else
echo is not a directory
fi
shift
done
该脚本对我来说工作正常,并显示了 PATH 变量中定义的所有易受攻击的路径。我还想根据上述结果自动执行正确设置 PATH 变量的过程。有什么快速方法可以做到这一点。
例如,在我的 Linux 框中,脚本给出的输出为:
/usr/bin/X11 is not a directory
/root/bin is not a directory
而我的 PATH 变量定义了这些,所以我想有一个删除机制,将它们从 root 的 PATH 变量中删除。许多冗长的想法浮现在脑海中。但请搜索一种快速且 "not so complex" 的方法。
以下可以完成全部工作,还可以删除重复的条目
export PATH="$(perl -e 'print join(q{:}, grep{ -d && !((stat(_))[2]&022) && !$seen{$_}++ } split/:/, $ENV{PATH})')"
我喜欢@kobame 的回答,但如果您不喜欢 perl
-依赖关系,您可以做类似的事情:
$ cat path.sh
#!/bin/bash
PATH="/root/bin:/tmp/groupwrite:/tmp/otherwrite:/usr/bin:/usr/sbin"
echo "${PATH}"
OIFS=$IFS
IFS=:
for path in ${PATH}; do
[ -d "${path}" ] || continue
paths=( "${paths[@]}" "${path}" )
done
while read -r stat path; do
[ "${stat:5:1}${stat:8:1}" = '--' ] || continue
newpath="${newpath}:${path}"
done < <(stat -c "%A:%n" "${paths[@]}" 2>/dev/null)
IFS=${OIFS}
PATH=${newpath#:}
echo "${PATH}"
$ ./path.sh
/root/bin:/tmp/groupwrite:/tmp/otherwrite:/usr/bin:/usr/sbin
/usr/bin:/usr/sbin
请注意,由于 stat
不可移植,因此不可移植,但它可以在 Linux(和 Cygwin)上运行。为了在 BSD 系统上工作,您必须调整格式字符串,其他 Unices 根本不附带 stat
OOTB(例如,Solaris)。
它不会删除不属于 root
但可以轻松添加的重复项或目录。后者只需要稍微调整循环,以便 stat
也 returns 所有者的用户名:
while read -r stat owner path; do
[ "${owner}${stat:5:1}${stat:8:1}" = 'root--' ] || continue
newpath="${newpath}:${path}"
done < <(stat -c "%A:%U:%n" "${paths[@]}" 2>/dev/null)
我建议您获得一本关于 Bash shell 脚本编写的好书。看起来您是通过查看 30 年前的系统 shell 脚本和黑客攻击来学习 Bash 的。这不是什么可怕的事情。事实上,它显示了主动性和出色的逻辑能力。不幸的是,它会将您引向一些非常糟糕的代码。
If 语句
在原版 Bourne shell 中,[
是一个命令。事实上,/bin/[
很难 link 到 /bin/test
。 test
命令是一种测试文件某些方面的方法。例如,如果 $file
是可执行的,test -e $file
会 return 一个 0
,如果不是,则 1
。
if
仅在其后执行命令,如果该命令 return 的退出代码为零,则 运行 then
子句,或者 else
子句(如果存在)如果退出代码不为零。
这两个是一样的:
if test -e $file
then
echo "$file is executable"
fi
if [ -e $file ]
then
echo "$file is executable"
fi
重要的是[
只是一个系统命令。 if
:
不需要这些
if grep -q "foo" $file
then
echo "Found 'foo' in $file"
fi
请注意,我只是 运行ning grep
,如果 grep
成功,我将重复我的声明。不需要 [ ... ]
。
到if
的快捷方式是使用列表运算符&&
和||
.例如:
grep -q "foo" $file && echo "我在 $file 中找到 'foo'"
与上面的if
语句相同。
从不解析 ls
你不应该解析 ls
命令。您应该改用 stat
。 stat
以易于解析的形式获取命令中的所有信息。
[ ... ]
对比 [[ ... ]]
前面提到过,在原版的谍影重重中shell,[
是一个系统命令。在 Kornshell 中,它是一个内部命令,Bash 也继承了它。
[ ... ]
的问题在于 shell 会在执行测试之前首先插入命令。因此,它容易受到各种 shell 问题的影响。 Kornshell 引入了 [[ ... ]]
作为 [ ... ]
的替代品,Bash 也使用它。
[[ ... ]]
允许 Kornshell 和 Bash 在 shell 插入命令之前评估参数 。例如:
foo="this is a test"
bar="test this is"
[ $foo = $bar ] && echo "'$foo' and '$bar' are equal."
[[ $foo = $bar ]] && echo "'$foo' and '$bar' are equal."
在 [ ... ]
测试中,shell 首先进行插值,这意味着它变为 [ this is a test = test this is ]
,这是无效的。在 [[ ... ]]
中,首先评估参数,因此 shell 理解它是 $foo
和 $bar
之间的测试。然后,对 $foo
和 $bar
的值进行插值。行得通。
For 循环和 $IFS
有一个名为 $IFS
的 shell 变量,它设置 read
和 for
循环如何解析它们的参数。通常,它设置为 space/tab/NL,但您可以修改它。由于每个 PATH 参数由 :
分隔,您可以设置 IFS=:"
,并使用 for
循环来解析您的 $PATH
.
<<<
重定向
<<<
允许您使用 shell 变量并将其作为 STDIN 传递给命令。这些都或多或少地做同样的事情:
statement="This contains the word 'foo'"
echo "$statement" | sed 's/foo/bar/'
statement="This contains the word 'foo'"
sed 's/foo/bar/'<<<$statement
中的数学Shell
使用 ((...))
允许您使用数学,其中一项数学函数是屏蔽。我使用掩码来确定是否在权限中设置了某些位。
比如我的目录权限是0755
,我和反对0022
,我可以看看是否设置了用户读写权限.注意前导零。这很重要,因此这些被解释为八进制值。
这是使用上面的代码重写的程序:
#! /bin/bash
grep -q "::" <<<"$PATH" && echo "Empty directory in PATH ('::')."
grep -q ":$" <<<$PATH && "PATH has trailing ':'"
#
# Fix Path Issues
#
path=$(sed -e 's/::/:/g' -e 's/:$//'<<<$PATH);
OLDIFS="$IFS"
IFS=":"
for directory in $PATH
do
[[ $directory == "." ]] && echo "Path contains '.'."
[[ ! -d "$directory" ]] && echo "'$directory' isn't a directory in path."
mode=$(stat -L -f %04Lp "$directory") # Differs from system to system
[[ $(stat -L -f %u "$directory") -eq 0 ]] && echo "Directory '$directory' owned by root"
((mode & 0022)) && echo "Group or Other write permission is set on '$directory'."
done
关于 PATH 漏洞,我不是 100% 确定你想要做什么或意味着什么。我不知道你为什么关心一个目录是否属于 root,如果 $PATH
中的条目不是目录,它不会影响 $PATH
。但是,我要测试的一件事是确保 $PATH
中的所有目录都是绝对路径。
[[ $directory != /* ]] && echo "Directory '$directory' is a relative path"
没有冒犯,但您的代码已完全损坏。您以一种……创造性的方式使用引号,但却是一种完全错误的方式。不幸的是,您的代码受到路径名扩展和分词的影响。使用不安全的代码来“保护”您的 PATH
.
真的很可惜
一个策略是(安全地!)将您的 PATH
变量拆分成一个数组,然后扫描每个条目。拆分是这样完成的:
IFS=: read -r -d '' -a path_ary < <(printf '%s:[=10=]' "$PATH")
查看我的 and How to split a string on a delimiter 答案。
使用此命令,您将拥有一个漂亮的数组 path_ary
,其中包含 PATH
.
的每个字段
然后您可以检查那里是否有空字段,或者 .
字段或相对路径:
for ((i=0;i<${#path_ary[@]};++i)); do
if [[ ${path_ary[i]} = ?(.) ]]; then
printf 'Warning: the entry %d contains the current dir\n' "$i"
elif [[ ${path_ary[i]} != /* ]]; then
printf 'Warning: the entry %s is not an absolute path\n' "$i"
fi
done
您可以添加更多 elif
,例如,检查条目是否为无效目录:
elif [[ ! -d ${path_ary[i]} ]]; then
printf 'Warning: the entry %s is not a directory\n' "$i"
现在,要检查权限和所有权,不幸的是,没有纯粹的 Bash 方式或 portable 方式进行。但是解析 ls
很可能不是一个好主意。 stat
可以工作,但已知在不同平台上有不同的行为。因此,您必须尝试适合您的方法。这是一个在 Linux:
上使用 GNU stat
的示例
read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}")
您需要检查 owner_id
是否为 0
(请注意,目录路径不属于 root 是可以的;例如,我有 /home/gniourf/bin
这很好!)。 perms
是八进制的,您可以通过位测试轻松检查 g+w
或 o+w
:
elif [[ $owner_id != 0 ]]; then
printf 'Warning: the entry %s is not owned by root\n' "$i"
elif ((0022&8#$perms)); then
printf 'Warning: the entry %s has group or other write permission\n' "$i"
注意使用8#$perms
强制Bash理解perms
为八进制数。
现在,要删除它们,您可以 unset path_ary[i]
当其中一个测试被触发时,然后将所有剩余的放回 PATH
:
else
# In the else statement, the corresponding entry is good
unset_it=false
fi
if $unset_it; then
printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}"
unset path_ary[i]
fi
当然,您将 unset_it=true
作为循环的第一条指令。
然后将所有内容放回 PATH
:
IFS=: eval 'PATH="${path_ary[*]}"'
我知道有些人会大声说 eval
是邪恶的,但这是在 Bash 中加入数组元素的规范(而且安全!)方式(注意单引号)。
最后,相应的函数可能如下所示:
clean_path() {
local path_ary perms owner_id unset_it
IFS=: read -r -d '' -a path_ary < <(printf '%s:[=17=]' "$PATH")
for ((i=0;i<${#path_ary[@]};++i)); do
unset_it=true
read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}" 2>/dev/null)
if [[ ${path_ary[i]} = ?(.) ]]; then
printf 'Warning: the entry %d contains the current dir\n' "$i"
elif [[ ${path_ary[i]} != /* ]]; then
printf 'Warning: the entry %s is not an absolute path\n' "$i"
elif [[ ! -d ${path_ary[i]} ]]; then
printf 'Warning: the entry %s is not a directory\n' "$i"
elif [[ $owner_id != 0 ]]; then
printf 'Warning: the entry %s is not owned by root\n' "$i"
elif ((0022 & 8#$perms)); then
printf 'Warning: the entry %s has group or other write permission\n' "$i"
else
# In the else statement, the corresponding entry is good
unset_it=false
fi
if $unset_it; then
printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}"
unset path_ary[i]
fi
done
IFS=: eval 'PATH="${path_ary[*]}"'
}
这种设计,加上 if/elif/.../else/fi
非常适合这个简单的任务,但用于更复杂的测试可能会很尴尬。例如,请注意我们必须在测试之前尽早调用 stat
,以便在测试后期可以使用信息,甚至在我们检查我们正在处理目录之前。
设计可能会通过使用一种意大利面条的可怕性来改变,如下所示:
for ((oneblock=1;oneblock--;)); do
# This block is only executed once
# You can exit this block with break at any moment
done
通常使用函数而不是 this 和函数中的 return
会好得多。但是因为在下面我还要检查多个条目,所以我需要查找 table(关联数组),并且有一个独立函数使用在某处定义的关联数组是很奇怪的否则……
clean_path() {
local path_ary perms owner_id unset_it oneblock
local -A lookup
IFS=: read -r -d '' -a path_ary < <(printf '%s:[=19=]' "$PATH")
for ((i=0;i<${#path_ary[@]};++i)); do
unset_it=true
for ((oneblock=1;oneblock--;)); do
if [[ ${path_ary[i]} = ?(.) ]]; then
printf 'Warning: the entry %d contains the current dir\n' "$i"
break
elif [[ ${path_ary[i]} != /* ]]; then
printf 'Warning: the entry %s is not an absolute path\n' "$i"
break
elif [[ ! -d ${path_ary[i]} ]]; then
printf 'Warning: the entry %s is not a directory\n' "$i"
break
elif [[ ${lookup[${path_ary[i]}]} ]]; then
printf 'Warning: the entry %s appears multiple times\n' "$i"
break
fi
# Here I'm sure I'm dealing with a directory
read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}")
if [[ $owner_id != 0 ]]; then
printf 'Warning: the entry %s is not owned by root\n' "$i"
break
elif ((0022 & 8#$perms)); then
printf 'Warning: the entry %s has group or other write permission\n' "$i"
break
fi
# All tests passed, will keep it
lookup[${path_ary[i]}]=1
unset_it=false
done
if $unset_it; then
printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}"
unset path_ary[i]
fi
done
IFS=: eval 'PATH="${path_ary[*]}"'
}
关于空格和全局字符以及内部换行符,所有这些都是非常安全的 PATH
;我唯一不喜欢的是使用外部(和非 portable)stat
命令。
下面是我检查根路径完整性的脚本,以确保 PATH 变量中没有漏洞。
#! /bin/bash
if [ ""`echo $PATH | /bin/grep :: `"" != """" ]; then
echo "Empty Directory in PATH (::)"
fi
if [ ""`echo $PATH | /bin/grep :$`"" != """" ]; then echo ""Trailing : in PATH""
fi
p=`echo $PATH | /bin/sed -e 's/::/:/' -e 's/:$//' -e 's/:/ /g'`
set -- $p
while [ """" != """" ]; do
if [ """" = ""."" ]; then
echo ""PATH contains ."" shift
continue
fi
if [ -d ]; then
dirperm=`/bin/ls -ldH | /bin/cut -f1 -d"" ""`
if [ `echo $dirperm | /bin/cut -c6 ` != ""-"" ]; then
echo ""Group Write permission set on directory ""
fi
if [ `echo $dirperm | /bin/cut -c9 ` != ""-"" ]; then
echo ""Other Write permission set on directory ""
fi
dirown=`ls -ldH | awk '{print }'`
if [ ""$dirown"" != ""root"" ] ; then
echo is not owned by root
fi
else
echo is not a directory
fi
shift
done
该脚本对我来说工作正常,并显示了 PATH 变量中定义的所有易受攻击的路径。我还想根据上述结果自动执行正确设置 PATH 变量的过程。有什么快速方法可以做到这一点。
例如,在我的 Linux 框中,脚本给出的输出为:
/usr/bin/X11 is not a directory
/root/bin is not a directory
而我的 PATH 变量定义了这些,所以我想有一个删除机制,将它们从 root 的 PATH 变量中删除。许多冗长的想法浮现在脑海中。但请搜索一种快速且 "not so complex" 的方法。
以下可以完成全部工作,还可以删除重复的条目
export PATH="$(perl -e 'print join(q{:}, grep{ -d && !((stat(_))[2]&022) && !$seen{$_}++ } split/:/, $ENV{PATH})')"
我喜欢@kobame 的回答,但如果您不喜欢 perl
-依赖关系,您可以做类似的事情:
$ cat path.sh
#!/bin/bash
PATH="/root/bin:/tmp/groupwrite:/tmp/otherwrite:/usr/bin:/usr/sbin"
echo "${PATH}"
OIFS=$IFS
IFS=:
for path in ${PATH}; do
[ -d "${path}" ] || continue
paths=( "${paths[@]}" "${path}" )
done
while read -r stat path; do
[ "${stat:5:1}${stat:8:1}" = '--' ] || continue
newpath="${newpath}:${path}"
done < <(stat -c "%A:%n" "${paths[@]}" 2>/dev/null)
IFS=${OIFS}
PATH=${newpath#:}
echo "${PATH}"
$ ./path.sh
/root/bin:/tmp/groupwrite:/tmp/otherwrite:/usr/bin:/usr/sbin
/usr/bin:/usr/sbin
请注意,由于 stat
不可移植,因此不可移植,但它可以在 Linux(和 Cygwin)上运行。为了在 BSD 系统上工作,您必须调整格式字符串,其他 Unices 根本不附带 stat
OOTB(例如,Solaris)。
它不会删除不属于 root
但可以轻松添加的重复项或目录。后者只需要稍微调整循环,以便 stat
也 returns 所有者的用户名:
while read -r stat owner path; do
[ "${owner}${stat:5:1}${stat:8:1}" = 'root--' ] || continue
newpath="${newpath}:${path}"
done < <(stat -c "%A:%U:%n" "${paths[@]}" 2>/dev/null)
我建议您获得一本关于 Bash shell 脚本编写的好书。看起来您是通过查看 30 年前的系统 shell 脚本和黑客攻击来学习 Bash 的。这不是什么可怕的事情。事实上,它显示了主动性和出色的逻辑能力。不幸的是,它会将您引向一些非常糟糕的代码。
If 语句
在原版 Bourne shell 中,[
是一个命令。事实上,/bin/[
很难 link 到 /bin/test
。 test
命令是一种测试文件某些方面的方法。例如,如果 $file
是可执行的,test -e $file
会 return 一个 0
,如果不是,则 1
。
if
仅在其后执行命令,如果该命令 return 的退出代码为零,则 运行 then
子句,或者 else
子句(如果存在)如果退出代码不为零。
这两个是一样的:
if test -e $file
then
echo "$file is executable"
fi
if [ -e $file ]
then
echo "$file is executable"
fi
重要的是[
只是一个系统命令。 if
:
if grep -q "foo" $file
then
echo "Found 'foo' in $file"
fi
请注意,我只是 运行ning grep
,如果 grep
成功,我将重复我的声明。不需要 [ ... ]
。
到if
的快捷方式是使用列表运算符&&
和||
.例如:
grep -q "foo" $file && echo "我在 $file 中找到 'foo'"
与上面的if
语句相同。
从不解析 ls
你不应该解析 ls
命令。您应该改用 stat
。 stat
以易于解析的形式获取命令中的所有信息。
[ ... ]
对比 [[ ... ]]
前面提到过,在原版的谍影重重中shell,[
是一个系统命令。在 Kornshell 中,它是一个内部命令,Bash 也继承了它。
[ ... ]
的问题在于 shell 会在执行测试之前首先插入命令。因此,它容易受到各种 shell 问题的影响。 Kornshell 引入了 [[ ... ]]
作为 [ ... ]
的替代品,Bash 也使用它。
[[ ... ]]
允许 Kornshell 和 Bash 在 shell 插入命令之前评估参数 。例如:
foo="this is a test"
bar="test this is"
[ $foo = $bar ] && echo "'$foo' and '$bar' are equal."
[[ $foo = $bar ]] && echo "'$foo' and '$bar' are equal."
在 [ ... ]
测试中,shell 首先进行插值,这意味着它变为 [ this is a test = test this is ]
,这是无效的。在 [[ ... ]]
中,首先评估参数,因此 shell 理解它是 $foo
和 $bar
之间的测试。然后,对 $foo
和 $bar
的值进行插值。行得通。
For 循环和 $IFS
有一个名为 $IFS
的 shell 变量,它设置 read
和 for
循环如何解析它们的参数。通常,它设置为 space/tab/NL,但您可以修改它。由于每个 PATH 参数由 :
分隔,您可以设置 IFS=:"
,并使用 for
循环来解析您的 $PATH
.
<<<
重定向
<<<
允许您使用 shell 变量并将其作为 STDIN 传递给命令。这些都或多或少地做同样的事情:
statement="This contains the word 'foo'"
echo "$statement" | sed 's/foo/bar/'
statement="This contains the word 'foo'"
sed 's/foo/bar/'<<<$statement
中的数学Shell
使用 ((...))
允许您使用数学,其中一项数学函数是屏蔽。我使用掩码来确定是否在权限中设置了某些位。
比如我的目录权限是0755
,我和反对0022
,我可以看看是否设置了用户读写权限.注意前导零。这很重要,因此这些被解释为八进制值。
这是使用上面的代码重写的程序:
#! /bin/bash
grep -q "::" <<<"$PATH" && echo "Empty directory in PATH ('::')."
grep -q ":$" <<<$PATH && "PATH has trailing ':'"
#
# Fix Path Issues
#
path=$(sed -e 's/::/:/g' -e 's/:$//'<<<$PATH);
OLDIFS="$IFS"
IFS=":"
for directory in $PATH
do
[[ $directory == "." ]] && echo "Path contains '.'."
[[ ! -d "$directory" ]] && echo "'$directory' isn't a directory in path."
mode=$(stat -L -f %04Lp "$directory") # Differs from system to system
[[ $(stat -L -f %u "$directory") -eq 0 ]] && echo "Directory '$directory' owned by root"
((mode & 0022)) && echo "Group or Other write permission is set on '$directory'."
done
关于 PATH 漏洞,我不是 100% 确定你想要做什么或意味着什么。我不知道你为什么关心一个目录是否属于 root,如果 $PATH
中的条目不是目录,它不会影响 $PATH
。但是,我要测试的一件事是确保 $PATH
中的所有目录都是绝对路径。
[[ $directory != /* ]] && echo "Directory '$directory' is a relative path"
没有冒犯,但您的代码已完全损坏。您以一种……创造性的方式使用引号,但却是一种完全错误的方式。不幸的是,您的代码受到路径名扩展和分词的影响。使用不安全的代码来“保护”您的 PATH
.
一个策略是(安全地!)将您的 PATH
变量拆分成一个数组,然后扫描每个条目。拆分是这样完成的:
IFS=: read -r -d '' -a path_ary < <(printf '%s:[=10=]' "$PATH")
查看我的
使用此命令,您将拥有一个漂亮的数组 path_ary
,其中包含 PATH
.
然后您可以检查那里是否有空字段,或者 .
字段或相对路径:
for ((i=0;i<${#path_ary[@]};++i)); do
if [[ ${path_ary[i]} = ?(.) ]]; then
printf 'Warning: the entry %d contains the current dir\n' "$i"
elif [[ ${path_ary[i]} != /* ]]; then
printf 'Warning: the entry %s is not an absolute path\n' "$i"
fi
done
您可以添加更多 elif
,例如,检查条目是否为无效目录:
elif [[ ! -d ${path_ary[i]} ]]; then
printf 'Warning: the entry %s is not a directory\n' "$i"
现在,要检查权限和所有权,不幸的是,没有纯粹的 Bash 方式或 portable 方式进行。但是解析 ls
很可能不是一个好主意。 stat
可以工作,但已知在不同平台上有不同的行为。因此,您必须尝试适合您的方法。这是一个在 Linux:
stat
的示例
read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}")
您需要检查 owner_id
是否为 0
(请注意,目录路径不属于 root 是可以的;例如,我有 /home/gniourf/bin
这很好!)。 perms
是八进制的,您可以通过位测试轻松检查 g+w
或 o+w
:
elif [[ $owner_id != 0 ]]; then
printf 'Warning: the entry %s is not owned by root\n' "$i"
elif ((0022&8#$perms)); then
printf 'Warning: the entry %s has group or other write permission\n' "$i"
注意使用8#$perms
强制Bash理解perms
为八进制数。
现在,要删除它们,您可以 unset path_ary[i]
当其中一个测试被触发时,然后将所有剩余的放回 PATH
:
else
# In the else statement, the corresponding entry is good
unset_it=false
fi
if $unset_it; then
printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}"
unset path_ary[i]
fi
当然,您将 unset_it=true
作为循环的第一条指令。
然后将所有内容放回 PATH
:
IFS=: eval 'PATH="${path_ary[*]}"'
我知道有些人会大声说 eval
是邪恶的,但这是在 Bash 中加入数组元素的规范(而且安全!)方式(注意单引号)。
最后,相应的函数可能如下所示:
clean_path() {
local path_ary perms owner_id unset_it
IFS=: read -r -d '' -a path_ary < <(printf '%s:[=17=]' "$PATH")
for ((i=0;i<${#path_ary[@]};++i)); do
unset_it=true
read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}" 2>/dev/null)
if [[ ${path_ary[i]} = ?(.) ]]; then
printf 'Warning: the entry %d contains the current dir\n' "$i"
elif [[ ${path_ary[i]} != /* ]]; then
printf 'Warning: the entry %s is not an absolute path\n' "$i"
elif [[ ! -d ${path_ary[i]} ]]; then
printf 'Warning: the entry %s is not a directory\n' "$i"
elif [[ $owner_id != 0 ]]; then
printf 'Warning: the entry %s is not owned by root\n' "$i"
elif ((0022 & 8#$perms)); then
printf 'Warning: the entry %s has group or other write permission\n' "$i"
else
# In the else statement, the corresponding entry is good
unset_it=false
fi
if $unset_it; then
printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}"
unset path_ary[i]
fi
done
IFS=: eval 'PATH="${path_ary[*]}"'
}
这种设计,加上 if/elif/.../else/fi
非常适合这个简单的任务,但用于更复杂的测试可能会很尴尬。例如,请注意我们必须在测试之前尽早调用 stat
,以便在测试后期可以使用信息,甚至在我们检查我们正在处理目录之前。
设计可能会通过使用一种意大利面条的可怕性来改变,如下所示:
for ((oneblock=1;oneblock--;)); do
# This block is only executed once
# You can exit this block with break at any moment
done
通常使用函数而不是 this 和函数中的 return
会好得多。但是因为在下面我还要检查多个条目,所以我需要查找 table(关联数组),并且有一个独立函数使用在某处定义的关联数组是很奇怪的否则……
clean_path() {
local path_ary perms owner_id unset_it oneblock
local -A lookup
IFS=: read -r -d '' -a path_ary < <(printf '%s:[=19=]' "$PATH")
for ((i=0;i<${#path_ary[@]};++i)); do
unset_it=true
for ((oneblock=1;oneblock--;)); do
if [[ ${path_ary[i]} = ?(.) ]]; then
printf 'Warning: the entry %d contains the current dir\n' "$i"
break
elif [[ ${path_ary[i]} != /* ]]; then
printf 'Warning: the entry %s is not an absolute path\n' "$i"
break
elif [[ ! -d ${path_ary[i]} ]]; then
printf 'Warning: the entry %s is not a directory\n' "$i"
break
elif [[ ${lookup[${path_ary[i]}]} ]]; then
printf 'Warning: the entry %s appears multiple times\n' "$i"
break
fi
# Here I'm sure I'm dealing with a directory
read perms owner_id < <(/usr/bin/stat -Lc '%a %u' -- "${path_ary[i]}")
if [[ $owner_id != 0 ]]; then
printf 'Warning: the entry %s is not owned by root\n' "$i"
break
elif ((0022 & 8#$perms)); then
printf 'Warning: the entry %s has group or other write permission\n' "$i"
break
fi
# All tests passed, will keep it
lookup[${path_ary[i]}]=1
unset_it=false
done
if $unset_it; then
printf 'Unsetting entry %s: %s\n' "$i" "${path_ary[i]}"
unset path_ary[i]
fi
done
IFS=: eval 'PATH="${path_ary[*]}"'
}
关于空格和全局字符以及内部换行符,所有这些都是非常安全的 PATH
;我唯一不喜欢的是使用外部(和非 portable)stat
命令。