资讯动态

文件名里的空格与星号:引号与展开这一层

发布时间:2026/9/29 11:53:51 来源:尧图企业网站定制
授权与合规声明本文全部操作对象均为自建隔离靶场本机容器或隔离虚拟机涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离不含任何攻击步骤、利用载荷与绕过手法请勿将文中环境指向任何非自有系统。一、命令看到的是一串被切好的字符串你在靶场里下载了一份题目附件文件名里带了一个空格。你把路径敲进命令结果不如预期——命令像是只认了文件名的一半另一半被当成了别的东西。同一类困惑还有两种很常见你敲了一条带*的命令本意是就那一个文件结果命令收到了一大串你从一个变量里取路径变量偏偏是空的命令的表现跟上次完全不一样。1.1 三个现象其实在问同一个问题这三种现象表面上看毫不相干一个是空格一个是星号一个是空变量。但它们问的是同一件事——你写下的那串字符和命令最终收到的东西到底是不是一回事。答案是不是。你写下的是给 shell 看的一段文字命令收到的是一串已经被切好、并且可能已经被替换过的字符串。中间隔着的那一层就是本文要讲的参数层。这一层的故障方式很有特点它往往不改变命令本身的逻辑只改变命令拿到几个字符串、每个字符串里有什么。于是现象看起来像命令好像不太对而不是我写的那行字有问题。1.2 参数层命令收到的是一份字符串列表先把这一层说清楚。你在终端里敲下的一切先交给 shell 由它解释shell 解释完再把结果作为若干条互相独立的字符串交给真正要执行的那个命令。命令自己从来没见过你的引号也没见过你的*和$——它看到的只有 shell 交给它的那份字符串列表。这就解释了很多看起来不讲道理的结果。命令认为参数不对往往不是命令有毛病而是 shell 交给它的个数和你想的不一样你以为是一个参数、里面含着一个空格shell 交过去的是两个参数、各自都不含空格。所以参数层的第一句结论是引号与展开这一层决定的是参数列表长什么样而不是命令怎么执行。调这一层调的是列表不是逻辑。1.3 本文的边界边界说三条。第一本文不写完整的 shell 脚本教程不涉及变量赋值、流程控制、函数这些东西只讲你敲下去的这条命令参数是怎么变成最终那个样子的。第二本文只讲这层机制本身不涉及任何针对参数校验的对抗写法也不涉及把字符串再当成代码去解释的那类用法——那是另一个话题容易出事本文一个字都不写。第三本文所有命令都是无害的演示与只读查看不改变你的靶场环境。二、引号到底在做什么2.1 官方给的一句话定义Bash 官方手册在 3.1.2 Quoting 一节里第一句话就把引号的作用说完了Quoting is used to remove the special meaning of certain characters or words to the shell.翻成中文是引用引号用来消除某些字符或词对 shell 的特殊含义。这句话值得逐词读。“消除”——引号不是添加什么而是减掉什么它减掉的对象是对 shell 的特殊含义。$、*、?、空格这些字符本身在 shell 眼里都是有身份的$意味着后面要接一个变量空格意味着这里是两个词的分界*意味着这里要匹配一批文件名。你把它们放进引号里等于告诉 shell这一段里把它们的特殊身份收走按普通字符处理。一个实用推论如果某个字符本来就没有特殊含义给它加引号也不会出错。引号是减不是加所以多加了引号通常不出问题少加了引号才会。2.2 官方点名的四种引用机制同一节里官方列出了全部四种机制There are four quoting mechanisms: the escape character, single quotes, double quotes, and dollar-single quotes.四种分别是转义字符反斜杠、单引号、双引号以及dollar-single quotes$…那种写法。前三种是本文要细讲的第四种本文只用一句带过它属于一种在引用内部还能表达特殊字符的写法日常排查参数问题时很少是核心你只要知道官方把它单列为一种机制、不要把它和单引号混为一谈就够了。官方把机制数说成四种这件事本身就有用它意味着这四种之外的写法都不叫引用机制不必自己再发明一套说法。2.3 反斜杠粒度最小的那一种反斜杠是四种机制里粒度最小的一种一次只作用于它后面紧挨着的那一个字符。所以它常和引号配合使用——引号管一段反斜杠管一个。⚠️代码待验证echo带\空格\的\名字echo带 空格 的 名字echo带 空格 的 名字上面三条命令最后交给echo的参数列表是同一种形状一个参数里面含空格。三种写法用三种机制达到了同一个目的这正是理解引号的好入口——机制不同目的相同把空格的特殊含义收走。本文代码块均为无害演示未在本机实际执行也不粘贴任何运行输出下同。三、单引号与双引号3.1 单引号连转义都无效官方在 3.1.2.2 Single Quotes 里的原话是Enclosing characters in single quotes () preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash.前半句说把字符放在单引号里会保留引号内每一个字符的字面值。注意措辞是每一个字符后面没有任何例外清单——这就是单引号最硬的地方它把引号里面所有东西的特殊含义都收走了$不展开反斜杠也只是反斜杠。后半句是最容易被初学者踩到的一条单引号不可以出现在单引号之间就算前面加了反斜杠也不行。为什么因为反斜杠在单引号里同样失去了它的特殊含义——它已经不是转义字符了只是一个普通的反斜杠字符。既然反斜杠不再能转义单引号也就无法逃出去引号只能一直延续到下一个单引号为止。由此得到一个可以记住的结论单引号里没有例外也没有逃生舱。需要在单引号内表达单引号本身时做法不是加反斜杠官方明确说不行而是把这段引号断开、用别的机制拼起来。具体拼法本文不演示只把加反斜杠不行这个结论记牢——它是单引号最反直觉的一处。3.2 双引号里只剩四个字符还特殊双引号的官方定义措辞和单引号明显不同Enclosing characters in double quotes () preserves the literal value of all characters within the quotes, with the exception of $, \, ‘\’, and, when history expansion is enabled, ‘!’.前半段同样是保留字面值但这次说的是所有字符后面紧跟一个with the exception of——除了四个东西$参数展开、命令替换、算术展开仍然会发生反引号命令替换的另一种写法仍然有效反斜杠转义作用有条件地保留下一节讲!在历史展开被启用的前提下仍然特殊。注意第四条那个限定语“when history expansion is enabled”当历史展开被启用时。它不是无条件成立的——在非交互式的环境里历史展开通常本来就不生效!也就回到普通字符。所以双引号里的!要不要小心这个问题答案是看当前这个 shell 有没有开历史展开脱开这个前提去谈没有意义。双引号里想写双引号本身用反斜杠转义——正是那份清单里的反斜杠能够管住的对象之一。3.3 双引号里的反斜杠看它后面跟着谁双引号里的反斜杠是个部分保留的角色。官方说它retains its special meaning only when followed by one of the following characters: $, \, ‘’, ‘\’, or newline.也就是说反斜杠只在后面紧跟这五个之一时才执行转义$、反引号、、\、以及换行符。后面跟的不是这几个字符时这个反斜杠原样保留——它不会消失也不会被当作转义。这条的实用价值在于解释一类参数里多出一个反斜杠的困惑你在双引号里写了一个反斜杠以为它会把后面那个字符转义掉结果这个反斜杠自己留在了参数里。原因就是它后面跟的字符不在那五个名单内。判断它到底还算不算转义字符只需要看后面那一个字符是谁。把这一章压成一张表你在引号里写的单引号…双引号…$变量原样保留不展开仍会展开反引号原样保留仍是命令替换写法仍有效反斜杠只是普通字符后面跟$反引号\换行 时转义否则原样保留!原样保留历史展开启用时仍特殊否则普通字符单引号本身不能出现加反斜杠也不行普通字符双引号本身普通字符用反斜杠转义空格被保留不再当作词的分界被保留不再当作词的分界四、顺序先分词再文件名展开4.1 分词发生在什么阶段参数列表在交给命令之前会经历一串处理其中两步最容易混淆而且有固定先后先做分词word splitting再做文件名展开filename expansion也就是通配符那一步。官方在 3.5.8 Filename Expansion 的开头就把这个先后写明了After word splitting, unless the -f option has been set, Bash scans each word for the characters *, ?, and [.第一个词就是After word splitting——在分词之后。这个先后关系直接解释了参数个数为什么会多出来。顺序是这样起作用的一段文本里既有空格又有*时shell先按空格把它切成若干词再对每个词单独做通配符匹配所以结果是每个词各自展开后拼起来的列表不是整段先匹配再切开。反过来一段文本如果没被分词比如整体在双引号里它不会被切开——但通配符这一步同样不发生因为那几个字符的特殊含义已被引号收走。4.2 IFS 未设与设为空串是两件事分词到底按什么切由变量IFS决定。官方的说法是shell 把$IFS的每一个字符都当作一个分隔符不是把整个值当一个整体而是字符逐个算。更关键的是两种没有值的情况官方把它们分得很清楚If IFS is unset, word splitting behaves as if its value were spacetabnewline...If the value of IFS is null, no word splitting occurs.两句放一起差别就出来了IFS未设置unset分词照常进行行为等同于它的值是空格、制表符、换行符IFS的值为空串设了但设成空的完全不做分词。这是最容易混的一对。直觉上没设置和设成空的差不多但在分词这一层它们的结果正好相反前者按默认的那几个字符去切后者一个都不切。所以觉得参数个数不对劲时IFS有没有被设过值得单独确认一眼——没设和设空要用两种问法去问。4.3 没有被展开的词不做分词分词还有一个适用范围上的限定官方写得很直接The shell scans the results of parameter expansion, command substitution, and arithmetic expansion that did not occur within double quotes for word splitting. Words that were not expanded are not split.第一句说的是范围shell 只去扫参数展开、命令替换、算术展开这三类结果而且只扫那些没有发生在双引号里的。第二句更短却是本章的要点没有被展开的词不做分词。这句话拆开了本文开头第二个现象你敲的*之所以能变出一串东西不是分词变出来的而是文件名展开变出来的而那些本来就没有任何字符需要展开的部分比如命令名本身、比如一个纯字符串参数分词对它们没有影响。把这一层拢一下分词处理的只是展开产生出来的文本*变出文件名列表是另一步引号的作用是让这两步都够不着里面的字符。三条规则各管一段混着看就会觉得引号怎么有时候管用有时候不管用。五、通配符什么时候是模式什么时候只是字符5.1 未被引用才当模式文件名展开这一步的判定标准官方给得非常明确Bash scans each word for the characters *, ?, and [. If one of these characters appears, and is not quoted, then the word is regarded as a pattern, and replaced with a sorted list of filenames matching the pattern.三个字符*、?、[。判定条件是那句and is not quoted——并且它没有被引用。三个字符只要被引号收走过特殊含义就没了这个词也就不会被当作模式而是原样作为一个普通字符串传下去。还有半句容易被忽略匹配结果会被排序之后再替换。所以展开出来的顺序不是你创建文件的顺序而是排过序的一批文件名。⚠️代码待验证ls/tmp/靶场-*# 未被引用星号被当成模式ls/tmp/靶场-*# 被双引号引用不再是模式按普通字符处理ls/tmp/靶场-*# 单引号同理本机未实测。三条命令的差别只在那个星号对 shell 还算不算特殊这一点上。5.2 没有匹配时默认原样保留通配符最反直觉的一条是没匹配上怎么办If no matching filenames are found, and the shell option nullglob is disabled, the word is left unchanged.默认行为是这个词原样留下来。你敲的*如果谁也没匹配到shell 不会把它变成空也不会中止而是把一个字面上带星号的字符串原封不动地交给命令。很多人的困惑正来自这里——他以为参数是一个正常的文件名其实那个参数从头到尾都带着一个星号而它此刻就是一个普通字符。这个默认行为是可以被开关改掉的。可以改掉它的选项有这么几个选项开了之后的效果nullglob没有匹配时这个词被移除而不是原样保留failglob没有匹配时让这条命令失败命令不执行nocaseglob匹配文件名时忽略大小写dotglob让.开头的文件也能被通配符匹配到见下节四个选项改的是不同的东西前两个管没匹配上怎么办第三个管匹配时区不区分大小写第四个管.开头的文件算不算在匹配范围里。它们都不影响被引用的字符不算模式这条基本规则——引号永远优先。5.3 点开头的文件与 GLOBIGNORE关于.开头的文件名官方有一条独立规则When a pattern is used for filename expansion, the character . at the start of a filename or immediately following a slash must be matched explicitly, unless the shell option dotglob is set.意思是当用模式去匹配文件名时出现在文件名开头、或者紧跟在斜杠之后的那个.必须被显式匹配除非开了dotglob。说人话就是一个通配符不会顺手把点开头的文件也捞进来你要那个点就得自己把它写出来。这解释了明明用了星号点开头的文件却一个都没出现——不是漏了而是它们本来就不在那次匹配范围内。还有一个用于从结果里剔除的机制GLOBIGNORE。它可以把已经匹配到的某些文件名从结果里去掉。注意它的方向和前面几个选项不同前面几个管的是能不能匹配到GLOBIGNORE管的是匹配到了之后要不要留下。⚠️代码待验证ls/tmp/.靶场配置# 显式写出那个点才可能命中点开头的名字ls/tmp/*靶场配置# 星号自己不会带上点开头的名字本机未实测仅作只读查看。文件名与引号速查卡把单引号、双引号、反斜杠三种机制各自的例外清单加上分词与通配符的先后顺序压成一页对着看。放在资料包里扫码即可获取六、空参数与参数列表6.1 显式空参数被保留未引用空展开被移除这一条官方同样给了两句对照Explicit null arguments () are retained and passed to commands as empty strings. Unquoted implicit null arguments, resulting from the expansion of parameters that have no values, are removed.两句讲的是两种空去向完全相反显式的空参数你亲手写了一个被保留以空字符串的身份传给命令。命令拿到的参数列表里那个位置上确实有东西只是它是空的。未引用的、隐式的空参数比如一个没有值的变量被展开被移除。那个位置什么都不剩命令根本不知道那里原本有个占位。这一对差别很实用。当你想占住一个位置就写当你发现我明明写了那么多参数命令却少收到一个就要想一想其中是不是有一个是未引用的空展开被悄悄拿掉了。所谓显式与隐式区别就在于那个空是不是你自己写出来的。⚠️代码待验证printf[%s]\n$EMPTY乙# 第一个是显式空仍在第二个视变量而定printf[%s]\n甲 乙 丙# 对照三个都非空本机未实测仅演示参数个数的差别。6.2$与$参数列表这一层里最被官方点名的一对是*和The special parameters * and have special meaning when in double quotes.注意这句话的重心它们的特殊含义是在双引号里才成立的。也就是说这两个东西一旦脱掉引号那份特殊含义就无从谈起——这才是$和$差别的来源。$展开成每个参数各自一个词等价于把每个参数分别用引号包起来再接上也就是$1 $2 …那种形状所以参数的个数被原样保住每个参数里的空格也各自被保住。而$没有引号罩着展开出来的文本还要走一遍分词——原本可能是一个带空格的参数在分词这一步就被切成了好几个。这就把第三章和第四章串起来了$之所以被反复推荐是因为它同时避开了两个坑——既没有被分词切开也没有把多个参数压成一个。⚠️代码待验证printf[%s]\n$# 每个参数各自一个词个数不变printf[%s]\n$# 展开后还要分词个数可能变本机未实测。这一对差别只有在参数本身含空格、或者本来为空时才看得出来。6.3$*与$$*和$都带引号但两者不是一回事。按官方口径$展开成每个参数各自一个词而$*展开成一个词。前者保住参数的个数后者把全部参数压成一个。放在命令里看差别很直白用$参数个数与原来一致用$*只收到一个参数。而$*和裸写的$*又不一样——后者还要再过一次分词那就是 6.2 节讲的那件事了。把这几小节压成一张表写法展开成什么参数个数$每个参数各自一个词与原来一致$展开后仍要分词可能变多$*全部压成一个词变成一个$*压成一个词后仍要分词可能再变七、带走物一页顺序表7.1 顺序表参数从你写的变成命令收到的中间的关键顺序是固定的。遇到命令拿到的参数不对时照着这张表从上往下问顺序这一步做什么问自己一句1反斜杠先收走它后面一个字符的特殊含义我的反斜杠后面跟着谁2引号收走一段里所有字符的特殊含义这段我到底有没有引上3参数展开把$变量变成文本双引号内除外变量当前是什么是空还是没设4对没在双引号里的展开结果做分词IFS是没设还是被设成了空5对未被引用的*?[做文件名展开有没有匹配到没匹配到就是原样保留6空参数去留留下未引用空展开移除我是想占住位置还是想让它消失7把这一串字符串交给命令命令收到的个数和我以为的一样吗这张表最需要记住的是第 4 步在第 5 步之前先分词再展开文件名。顺序反了很多现象就解释不通。7.2 三条可带走的动作先数参数再研究命令。命令认为参数不对时第一个要确认的不是命令的用法而是shell 交给它的到底是几个字符串。多数时候问题在这一层。把没设和设成空当成两个问题问。IFS如此别的变量也如此——两者的结果常常正好相反。无匹配的通配符不会消失它会原样留下。看到参数里带着星号先想想是不是这一步没匹配到。7.3 边界与待验证要坦白三点。第一本文只覆盖参数层不覆盖命令怎么被找到另一层也不覆盖输出去了哪里同样是另一层层与层之间不互相替代。第二本文引用的 Bash 官方手册口径截至 2026-09-27 成立手册会继续更新隔一段时间要自己回头核一遍。第三有几处本文没有拿到官方逐字依据一律标待验证不替它下结论。以下几处本文只标注、不推断不同 shell 实现非 Bash在某些细节上是否与 Bash 手册逐字一致本文未逐一核验dollar-single quotes的完整语义本文未展开只按官方四种机制之一这句口径带过以及本文全部代码块均未在本机实际执行均为无害演示与只读查看。参数层排查顺序卡把 7.1 那张顺序表和三条动作抄成一页下次遇到命令拿到的参数不对时按顺序走一遍。放在资料包里扫码即可获取附表 A本文引用事实与官方出处对照表#事实照口径逐字原文一手出处核验日期本文位置1Quoting is used to remove the special meaning of certain characters or words to the shell.Bash 官方手册 3.1.2 Quoting — https://www.gnu.org/software/bash/manual/html_node/Quoting.html2026-09-27第二章2There are four quoting mechanisms: the escape character, single quotes, double quotes, and dollar-single quotes.同第 1 行2026-09-27第二章3Enclosing characters in single quotes () preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash.Bash 官方手册 3.1.2.2 Single Quotes — https://www.gnu.org/software/bash/manual/html_node/Single-Quotes.html2026-09-27第三章4Enclosing characters in double quotes () preserves the literal value of all characters within the quotes, with the exception of $, \, ‘\’, and, when history expansion is enabled, ‘!’.Bash 官方手册 3.1.2.3 Double Quotes — https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html2026-09-27第三章5双引号内反斜杠retains its special meaning only when followed by one of the following characters: $, \, ‘’, ‘\’, or newline.同第 4 行2026-09-27第三章6The special parameters * and have special meaning when in double quotes.同第 4 行2026-09-27第六、七章7The shell scans the results of parameter expansion, command substitution, and arithmetic expansion that did not occur within double quotes for word splitting. Words that were not expanded are not split.Bash 官方手册 3.5.7 Word Splitting — https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html2026-09-27第四章8The shell treats each character of $IFS as a delimiter...同第 7 行2026-09-27第四章9If IFS is unset, word splitting behaves as if its value were spacetabnewline...同第 7 行2026-09-27第四章10If the value of IFS is null, no word splitting occurs.同第 7 行2026-09-27第四章11Explicit null arguments () are retained and passed to commands as empty strings. Unquoted implicit null arguments, resulting from the expansion of parameters that have no values, are removed.同第 7 行2026-09-27第六章12After word splitting, unless the -f option has been set, Bash scans each word for the characters *, ?, and [. If one of these characters appears, and is not quoted, then the word is regarded as a pattern, and replaced with a sorted list of filenames matching the pattern.Bash 官方手册 3.5.8 Filename Expansion — https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html2026-09-27第四、五章13If no matching filenames are found, and the shell option nullglob is disabled, the word is left unchanged.另failglob使命令不执行nocaseglob忽略大小写匹配同第 12 行2026-09-27第五章14When a pattern is used for filename expansion, the character . at the start of a filename or immediately following a slash must be matched explicitly, unless the shell option dotglob is set.同第 12 行2026-09-27第五章15GLOBIGNORE可把匹配到的某些文件名从文件名展开的结果里剔除。同第 12 行2026-09-27第五章待验证非 Bash 的其它 shell 实现在引号与展开的细节上是否与 Bash 官方手册逐字一致——本次未逐一核验本文不作跨实现的结论待验证项2026-09-27第三章、第七章待验证dollar-single quotes的完整语义——本文未展开只按官方四种机制之一这句口径带过待验证项2026-09-27第二章待验证本文全部代码块均未在本机实际执行内容为无害演示与只读查看动作已逐块标注待验证项2026-09-27全文说明第 1–15 行为已核验的官方一手事实核验日期 2026-09-27。正文中凡出现最新 / 目前 / 已这类表述一律带截至 2026-09-27。上述待验证项本文只标注、不给结论。附表 B术语速查表术语一句话解释参数层命令最终收到的那份字符串列表所处的层本文只讲这一层引用Quoting消除某些字符或词对 shell 的特殊含义官方给出的机制共有四种单引号保留引号内每一个字符的字面值单引号本身不能出现在单引号之间加反斜杠也不行双引号保留几乎所有字符的字面值例外是$、反引号、反斜杠以及历史展开启用时的!dollar-single quotes官方列出的第四种引用机制本文只按这句口径带过不展开转义字符即反斜杠一次只作用于它后面紧挨着的那一个字符分词word splitting按$IFS的每个字符切分展开产生的文本未被展开的词不参与IFS决定按什么切分的变量未设置时按空格、制表符、换行符处理值为空串时完全不切文件名展开把未被引用的*?[当作模式替换成排序后的匹配文件名列表原样保留通配符没有匹配到文件名时的默认行为那个词带着星号原封不动传下去nullglob/failglob两个改变没匹配上怎么办的选项前者移除该词后者让命令失败且不执行nocaseglob匹配文件名时忽略大小写的选项dotglob让.开头的文件也能被通配符匹配到的选项GLOBIGNORE把已匹配到的某些文件名从展开结果里剔除的机制显式空参数你自己写出来的被保留并以空字符串传给命令隐式空参数未引用的空展开被移除那个位置什么也不剩$/$*前者展开成每个参数各自一个词后者展开成一个词待验证官方没有声明、本文也未实测的命题只标注、不下结论写在最后这篇用到的资料写这篇文章时我把 Bash 官方手册的 Quoting、Single Quotes、Double Quotes、Word Splitting、Filename Expansion 几节连起来读了一遍最有用的一句反而是最不起眼的那半句——After word splitting。先分词、再文件名展开顺序记牢了很多参数怎么多出来一个的困惑就自己解开了。顺手也整理了几份配套的东西文件名与引号速查卡三种引用机制各自的例外清单加上分词与通配符的先后顺序Web 安全学习路线图从基础打牢到安全管理四个阶段各学什么靶场环境对照表几个常见靶场在容器与宿主机之间的目录对应关系资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「靶场」优先通过。拿到之后建议先看文件名与引号速查卡那一份下次再遇到文件名里带空格、命令却只认了一半先按那张顺序表走一遍再决定从哪一步动手。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑