是否存在一个足够病态的文件名或文件名集合,其约束条件是它必须具有给定的前缀和后缀,比如file_<your_stuff_here>.txt
这会使在 bash 脚本中评估或反引号 ls 的输出变得危险?我们所说的“危险”意味着它允许任何拥有相关目录文件创建权限的人运行任何命令。
例如,让 bash 脚本只是
#!/usr/bin/env bash
hello=(`ls -t`)
看起来文件系统通过将文件名用引号括起来来清理特殊字符,例如空格。它会捕获所有这些东西吗?
答案1
文件名...评估危险
当然。
$ touch 'file $(date >&2).txt'
$ bash -c 'eval ls *'
Wed 03 Feb 2021 06:07:08 PM EET
ls: cannot access 'file': No such file or directory
ls: cannot access '.txt': No such file or directory
别乱eval
塞东西
或者在 bash 脚本中反引号 ls 的输出?
我不太确定这意味着什么。
如果你的意思是你所举的例子,
hello=(`ls -t`)
或者使用更理智的语法,
files=( $(ls -t) )
然后你只需将 wordsplit 的输出获取ls
到数组即可。
$ declare -p files
declare -a files=([0]="file" [1]="\$(date" [2]=">&2).txt")
即使在可能的命令注入之前,这里最大的问题是文件名中的空格破坏了它,我们得到了两个数组条目,而不是一个。请参阅有关页面解析 ls在格雷格的维基上。不,你不能通过添加引号来解决这个问题,分词不是那样的。
所以,不要ls
在那里使用。只需让 shell 生成文件名列表即可:
files=(*)
declare -p files
declare -a files=([0]="file \$(date >&2).txt")
这里唯一的问题是 Bash 没有提供按日期对文件进行排序的好方法,所以ls -t
是诱人的。好的选择是将日期放在文件名本身中所以默认排序让您按日期排序, 或者使用 Zsh,因为它可以按日期排序。或者做丑陋的黑客解决该问题(警告,我尚未测试该解决方案)。
eval
如果你需要将文件名传递给任何只需要 shell 脚本的东西,也会出现同样的问题,比如
ssh somehost "do something with $file" # wrong
ssh somehost "do something with '$file'" # still wrong, the name
# could contain single ticks
看起来文件系统通过将文件名用引号括起来来清理特殊字符,例如空格。它会捕获所有这些东西吗?
哦,亲爱的,哦不,不是这样。如果文件系统做了一些事情来阻止将特殊字符存储到 shell,那么 unix.SE 上的一半帖子就不需要了。
如果你想要承受太多知识带来的痛苦,请阅读大卫·惠勒 (David Wheeler) 的一篇文章:修复 Unix/Linux/POSIX 文件名:控制字符(例如换行符)、前导破折号和其他问题
还有他的另一张,Shell 中的文件名和路径名:如何正确执行。许多相关主题也已在 unix.SE 上进行了讨论。
此外,引号甚至没有帮助。
$ touch '"quoted name"' 'othername' # two files
$ printf "%s\n" $(ls) # oops
othername
"quoted
name"
$ printf "%s\n" * # this works better
othername
"quoted name"
因为当发生分词时,引号只是一个常规字符。 (除非您设置IFS
包含引号,这可能只会使情况变得更糟。)此外,即使名称用引号引起来,它也会中间仍然可以有引号,打破这一点。您还需要注意转义或正确引用它们。
是 GNU ls 进行引用,取决于版本和设置:
$ ls -l
total 0
-rw-rw-r-- 1 ilkkachu ilkkachu 0 Feb 3 18:30 othername
-rw-rw-r-- 1 ilkkachu ilkkachu 0 Feb 3 18:30 '"quoted name"'
这与 相同ls --quoting-style=shell
。顺便说一句,对于所有换行符、美元符号和引号,它似乎都是正确的。但你相信它能做对吗?如果你这样做,和如果您知道如何正确使用它,您也许可以使用它来获取排序列表。
答案2
hello=(`ls -t`)
这种形式看起来很安全。 Bash 将仅对命令替换的结果执行 split + glob,而不将其解释为语法:
a=(`echo '[0]=1'`)
typeset -p a
declare -a a=([0]="[0]=1") # aha!
然而,(晦涩的)declare "var=val"
或local "var=val"
形式则不然,因为它们的工作方式类似于eval "var=val"
:
cd "$(mktemp -d)"
touch '$(yes BOOBS >&2&)'
declare -a "a=($(ls))"
BOOBS
BOOBS
BOOBS
...
与declare -a a="($(ls))"
、declare -a a="(`ls`)"
等相同。
或者甚至与未引用的形式也是如此,前提是变量已经声明为数组:
cd "$(mktemp -d)"
a=(1 2 3)
touch '($(yes BOOBS >&2&))'
declare a=$(ls) # I forgot that a was an array
BOOBS
BOOBS
BOOBS
...