给定下面的脚本,如何确保参数仅包含有效的文件名/home/charlesingalls/
,而不包含路径 ( ../home/carolineingalls/
) 或通配符等?
我只希望脚本能够从给定的硬编码目录中删除单个文件。该脚本将以特权用户身份运行。
#!/bin/bash
rm -f /home/charlesingalls/"$1"
答案1
这个答案假设$1
允许包含子目录。如果您对更简单的情况感兴趣,其中$1
应该是一个简单的目录名称,那么请参阅其他答案之一。
用双引号引起来的通配符不会被扩展。由于$1
is 用双引号引起来,因此通配符不是问题。
和 符号链接都../
可以掩盖真实的文件的位置。下面显示的是确定文件是否确实位于我们想要的路径下(而不仅仅是表面上)的测试。
较新的系统:使用realpath
至于查明文件是否确实存在 /home/charlesingalls/
,您可以使用realpath
:
realpath --relative-base=/home/charlesingalls/ "/home/charlesingalls/$1" | grep -q '^/' && exit 1
exit 1
如果 指定的文件$1
位于目录下以外的任何位置,则上面的代码将运行/home/charlesingalls/
。 realpath
规范化整个路径,消除符号链接和../
.
realpath
是 GNU coreutils 的一部分,应该可以在任何 Linux 系统上使用。
realpath
需要GNU coreutils 8.15(2012 年 1 月)或更高版本。
例子
演示 realpath 如何../
确定文件的真实位置(例如,-q
省略 grep 选项,以便 grep 的实际输出可见):
$ touch /tmp/test
$ realpath --relative-base=$HOME "$HOME/../../tmp/test" | grep '^/' && echo FAIL
/tmp/test
FAIL
演示它如何遵循符号链接:
$ ln -s /tmp/test ~/test
$ realpath --relative-base=$HOME "$HOME/test" | grep '^/' && echo FAIL
/tmp/test
FAIL
旧系统:使用readlink -e
readlink
还能够圆锥化路径,遵循符号链接 和../
:
readlink -e "$HOME/test" | grep -q "^$HOME" || exit 1
使用相同的示例文件:
$ readlink -e "$HOME/../../tmp/test" | grep "$HOME" || echo FAIL
FAIL
$ readlink -e "$HOME/test" | grep "^$HOME" || echo FAIL
FAIL
除了在较旧的 GNU 系统上可用之外,readlink
还可以在 BSD 上使用 的版本。
答案2
如果您只想删除其中的文件/home/charlesingalls
(而不是子目录中的文件),那么很简单:只需检查参数是否不包含/
.
case "$1" in
*/*) echo 1>&2 "Refusing to remove a file in another directory"; exit 2;;
*) rm -f /home/charlesingalls/"$1";;
esac
rm
即使参数为.
or或 空,该命令也会运行..
,但在这种情况下,rm
删除目录会失败,不会造成任何损害。
通配符此处不相关,因为不执行通配符扩展。
即使存在符号链接,这也是安全的:如果文件是符号链接,则符号链接(位于 中/home/charlesingalls
)将被删除,并且该链接的目标不受影响。
请注意,这假设/home/charlesingalls
不能移动或更改。如果目录是在脚本中硬编码的,那应该没问题,但如果它是根据变量确定的,则在命令rm
运行时该确定可能不再有效。
基于附加信息如果参数是虚拟主机名,则应该将其列入白名单而不是黑名单:检查该名称是否是合理的虚拟主机名,而不仅仅是禁止斜杠。我检查名称是否以小写字母或数字开头,并且不包含小写字母、数字、点和破折号以外的字符。
LC_CTYPE=C LC_COLLATE=C
case "$1" in
*[!-.0-9a-z]*|[!0-9a-z]*) echo >&2 "Invalid host name"; exit 2;;
*) rm -f /home/charlesingalls/"$1";;
esac
答案3
如果要完全禁止路径,最简单的方法是测试变量是否包含斜杠 ( /
)。在bash中:
if [[ "$1" = */* ]] ; then...
但这将阻止所有路径,包括foo/bar
.您可以改为进行测试..
,但这会留下符号链接指向目标路径之外的目录的可能性。
如果您只想允许删除单个文件,我认为您不应该使用rm -r
.
此外,根据您正在执行的操作,您可以使用系统的文件权限来仅允许删除用户可以自行删除的文件。像这样的东西:
su charlesingalls -c "rm /home/charlesingalls/'$1'"
尽管正如@Gilles评论的那样,这有一个引用问题:如果包含单引号,它将失败,$1
因此应该首先测试该变量(例如,if [[ "$1" = *\'* ]] ; then fail...
或者通过将一组合理的字符列入白名单),或者通过的文件名环境变量,例如
file="$1" su charlesingalls -c 'rm "/home/charlesingalls/$file"'