
正则表达式不为空使用教程:新手完成第一张作品的方法
“不为空”看起来只是一个简单要求:输入框里不能什么都没有。但写成正则表达式时,首先要回答一个问题:空格算不算内容?换行算不算内容?字符串不存在和字符串存在但长度为零,是不是一回事?这些边界没有说清楚,正则就容易看起来能用,实际却放过了不该通过的输入。
这篇教程从一个最小的非空正则开始,逐步完成一份可以运行的输入校验示例。读完后,你会知道什么时候用 .+,什么时候需要判断空白,如何避免只匹配到字符串的一部分,以及为什么正则校验不能替代安全防护。
一、先说清楚“不为空”是什么意思
对字符串来说,常见的“为空”要求至少有三种。它们看起来相近,实际判断结果不同:
- 非空字符串:至少包含一个字符。
""不通过," "(一个空格)通过。 - 非空白字符串:至少包含一个非空白字符。空格、制表符或换行组成的字符串不通过。
- 字段必填:字段不能缺失,也不能是空字符串。是否拒绝纯空白输入,还要由业务规则决定。
因此,在动手写表达式前,先把规则写成一句明确的话。例如:“昵称至少有一个字符,允许用户只输入空格”与“昵称必须至少有一个非空白字符”是两条不同规则。
二、第一张作品:判断字符串是否至少有一个字符
最常见的表达式是:
^.+$
其中,^ 表示字符串或行的开头,$ 表示字符串或行的结尾;. 匹配一个字符,+ 表示前面的内容出现一次或多次。因此,.+ 要求至少匹配一个字符。
不过,点号 . 在不少正则引擎中默认不匹配换行符。也就是说,^.+$ 通常能匹配 "abc",也能匹配一个空格,但可能无法匹配包含换行的多行字符串。若业务里的“任意字符”也包括换行,可以改用能明确覆盖换行的写法,或者开启对应引擎的单行模式(常称 dotAll,具体名称依语言而异)。
在 JavaScript 中,一种不依赖点号换行选项的写法是:
/^[\s\S]+$/
\s 匹配空白字符,\S 匹配非空白字符;两者合在一起覆盖常见正则引擎所识别的字符,包括换行。加上量词 + 后,要求至少有一个字符。注意,这里的目标是判断字符串是否非空,不是要求它包含非空白字符。
在 JavaScript 中运行
把下面代码粘贴到浏览器开发者工具的控制台,或保存成 .js 文件后用 Node.js 运行:
const nonEmpty = /^[\s\S]+$/;
const samples = [
"",
"hello",
" ",
"\n",
"hello\nworld"
];
for (const value of samples) {
console.log(JSON.stringify(value), nonEmpty.test(value));
}
结果中,空字符串返回 false;普通文字、空格、换行和多行文本都返回 true。这正是“至少一个字符”的语义:空白也是字符。
如果输入变量可能不是字符串,先做类型处理。正则表达式的测试方法会把某些非字符串值转换成字符串,例如数字 0 会变成 "0",而 null 会变成 "null"。这通常不是表单校验想要的行为。
function isNonEmptyString(value) {
return typeof value === "string" && /^[\s\S]+$/.test(value);
}
console.log(isNonEmptyString("")); // false
console.log(isNonEmptyString("hello")); // true
console.log(isNonEmptyString(null)); // false
console.log(isNonEmptyString(0)); // false
三、如果空格也不算内容
表单校验中更常见的需求是:不能提交空白字符串。此时,单纯的 .+ 不够,因为空格也能满足“至少一个字符”。可以要求字符串中至少出现一个非空白字符:
^(?=[\s\S]*\S)[\s\S]+$
这里的 (?=...) 是正向先行断言,只检查后面的条件是否成立,不消耗字符。[\s\S]*\S 表示从当前位置往后可以有任意字符,最终必须能找到一个非空白字符。后面的 [\s\S]+ 再要求整个内容至少有一个字符。
在许多场景里,应用代码先去除首尾空白,再判断长度会更好读,也更容易按需求调整:
function hasVisibleContent(value) {
return typeof value === "string" && value.trim().length > 0;
}
console.log(hasVisibleContent("")); // false
console.log(hasVisibleContent(" ")); // false
console.log(hasVisibleContent("\n\t")); // false
console.log(hasVisibleContent(" hi ")); // true
trim() 不会修改原字符串,而是返回去掉首尾空白后的新字符串。它适合表达“首尾空白不算内容”的规则,但不要未经确认就把修剪后的结果存回去:某些字段中的前后空格可能有实际意义。不同语言和运行环境对空白字符的定义也可能有差异;如果产品需要精确规定哪些字符算空白,应明确列出并测试这些字符,而不是只依赖一个模糊的“空白”说法。
四、在 Python 中做同样的检查
Python 的正则模块叫 re。检查任意字符串至少有一个字符,可以使用 re.fullmatch,并用 [\s\S]+ 把换行也包括在内:
import re
non_empty = re.compile(r"[\s\S]+")
samples = ["", "hello", " ", "\n", "hello\nworld"]
for value in samples:
print(repr(value), bool(non_empty.fullmatch(value)))
这里特意使用 fullmatch,意思是要求整个字符串都符合表达式。另一种清晰的写法是直接检查字符串长度:
def is_non_empty_string(value):
return isinstance(value, str) and len(value) > 0
print(is_non_empty_string("")) # False
print(is_non_empty_string("hello")) # True
print(is_non_empty_string(None)) # False
如果规则是拒绝全空白输入,Python 字符串的 strip() 可以用于清理两端空白后判断:
def has_visible_content(value):
return isinstance(value, str) and bool(value.strip())
print(has_visible_content(" ")) # False
print(has_visible_content("\n\t")) # False
print(has_visible_content(" hi ")) # True
只为判断是否为空时,使用字符串长度或 strip() 往往比正则更直接。正则更适合在一个表达式中同时描述字符集合、长度或格式等多个条件;不要为了“用了正则”而把简单判断写复杂。
五、锚点不是装饰:避免只匹配到一部分
验证整段输入时,必须确认使用的 API 是“整串匹配”,还是“找到一处匹配即可”。例如,JavaScript 的 RegExp.prototype.test() 只要在目标字符串中找到符合表达式的位置,就会返回成功。下面的表达式没有锚点:
/hello/.test("say hello") // true
这对于搜索是合理的,却不等于验证整个输入。验证时通常使用 ^ 和 $,或调用明确执行整串匹配的 API。Python 的 re.search 是搜索任意位置,re.fullmatch 则要求整个字符串匹配。选择 API 时要按用途判断,不要只看正则本身。
还有一个容易忽略的细节:不同引擎对 $ 的处理存在差别。在一些模式或实现中,它可能允许在末尾换行之前匹配。若必须精确表达“到字符串绝对结尾”,应查看所用语言的正则文档,使用该引擎提供的严格结尾锚点,或直接使用整串匹配 API。比如 Python 的 fullmatch 对整串验证通常更直观。不要把某一种语言的锚点行为直接套用到所有语言。
JavaScript 的全局标志 g 和粘连标志 y 也会影响 test():这类正则会维护 lastIndex,重复测试时可能出现结果交替的情况。做简单的布尔校验时,通常不要给表达式加 g 或 y。
六、做一张可运行的表单校验小作品
下面是一个完整的浏览器示例。规则是:昵称必须是字符串,并且去掉首尾空白后至少有一个字符。把代码保存为 non-empty-demo.html,再用浏览器打开即可运行。
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<title>非空输入校验示例</title>
<label for="nickname">昵称</label>
<input id="nickname" autocomplete="off">
<button id="submit">提交</button>
<p id="message" aria-live="polite"></p>
<script>
const input = document.querySelector("#nickname");
const message = document.querySelector("#message");
const button = document.querySelector("#submit");
function hasVisibleContent(value) {
return typeof value === "string" && value.trim().length > 0;
}
button.addEventListener("click", () => {
if (!hasVisibleContent(input.value)) {
message.textContent = "请输入至少一个非空白字符。";
input.focus();
return;
}
message.textContent = "校验通过。";
});
</script>
</html>
这个示例用的是 trim(),而不是非空正则,因为规则恰好是“至少一个非空白字符”,代码也因此一眼可读。若需求改为“空格也算有效输入”,可以把判断改成 input.value.length > 0,或者使用前面的非空表达式。
示例中的提示文字通过 textContent 写入页面,而不是把用户输入拼进 innerHTML。这可以避免把输入内容当成 HTML 解析。真实应用仍应根据框架和上下文正确处理用户数据,不能仅凭一个前端例子得出系统已安全的结论。
七、HTML 的 required 和正则校验有什么区别
HTML 表单可以给输入框添加 required 属性,让浏览器执行基础的必填校验:
<form>
<input name="nickname" required>
<button>提交</button>
</form>
它适合提供浏览器原生的表单约束和用户提示,但“必填”的具体行为应在目标浏览器中验证。尤其要注意,必填属性并不等于“去掉空白后仍有内容”:由空格组成的值在常见浏览器中并不会因为 required 自动被当成空字符串拒绝。
若业务规定纯空白不合格,需要额外的客户端逻辑,并且服务器也要执行对应校验。浏览器端校验可以改善体验,但用户能够绕过它,直接向服务端发送请求。
八、长度、Unicode 与“一个字符”的边界
“至少一个字符”在不同环境里可能不等于“用户看见一个完整字符”。例如,某些带组合符号的文字、表情符号或由多个码点组成的字形,在用户看来是一个字符,但程序可能把它视为多个代码单元或多个码点。JavaScript 的字符串 length 使用 UTF-16 代码单元计数;Python 的字符串长度通常按 Unicode 码点计数。这两者都不总是等于用户感知的字素簇数量。
因此,对于“输入不能是空字符串”这种基础规则,上述检查足够直接;如果产品要求限制“用户感知字符数”,需要使用支持字素簇分段的库或语言 API,并说明计数口径。正则的 + 也不自动代表“一个用户眼中的字符”。
同理,空白字符集合会受到语言和引擎定义影响。若规则是“仅 ASCII 空格不算内容”,就应明确检查 ASCII 空格;若要排除所有 Unicode 空白,则应选定运行环境,并对实际涉及的字符做测试。不要未经定义就把“空格”“空白”和“不可见字符”当作同一个概念。
九、常见错误清单
- 用
.*判断非空。*表示零次或多次,因此空字符串也能匹配。要要求至少一次,使用+,并确认换行处理符合需求。 - 以为
.+会拒绝空格。点号通常会匹配空格,所以它判断的是非空,不是非空白。 - 忘记做整串验证。缺少锚点或调用了搜索 API,可能只要输入中有一小段符合就通过。确认使用了适合整串匹配的锚点或 API。
- 误解点号与换行。默认点号常常不匹配换行。单行模式、点号行为和锚点在不同引擎中有差异;需要覆盖换行时要明确处理。
- 把缺失值交给正则转换。先检查输入类型和字段是否存在,再检查字符串内容。不要依赖自动类型转换产生的结果。
- 不区分校验与清理。判断首尾空白是否存在,和是否要删除这些空白,是两项不同操作。未经业务允许,不要悄悄修改用户输入。
- 在 JavaScript 校验正则上随手加
g。反复调用test()时,全局正则可能受lastIndex影响。布尔校验通常不需要全局标志。 - 只在前端校验。前端校验可以被关闭或绕过。服务端必须独立执行业务规则。
十、安全用途:正则能做什么,不能做什么
非空校验是输入格式校验,不是防攻击方案。它不能替代权限检查、HTML 转义、SQL 参数化、路径校验或业务规则验证。比如,确认字符串非空并不能证明用户有权提交该内容,也不能阻止内容在错误的输出上下文中造成问题。
对用户可控的输入,正则本身也要写得简单、可预测。某些包含复杂嵌套重复或歧义回溯的表达式,可能在特定长输入上消耗大量时间,形成正则拒绝服务风险(ReDoS)。本文使用的非空模式很简单,通常不会产生这类典型的灾难性回溯结构;但这不代表所有“验证正则”都天然安全。处理不可信数据时,应结合输入长度限制、正则复杂度、引擎特性和运行超时等措施评估。
最实用的做法是:先规定允许的输入范围,再在客户端提供友好提示,最后在服务端用相同的业务规则重新验证。正则只承担它擅长的那一部分,不要让它代替整个安全设计。
总结:先定规则,再选表达式
如果需求只是“字符串至少有一个字符”,可以从 ^.+$ 开始,但要考虑点号是否匹配换行;在 JavaScript 中也可以用 /^[\s\S]+$/ 明确覆盖换行。若空格和换行都不能算有效内容,可以用非空白判断,或更直白地用 trim().length > 0。验证整段输入时,使用整串匹配方式;遇到缺失值、Unicode 字符和前后端安全问题,则分别处理,不要把它们混成一个正则问题。
完成第一张“作品”的关键不是写出最复杂的表达式,而是能用几组测试输入证明规则正确:空字符串、普通文字、纯空格、换行和多行文本都测一遍。把这些边界写进测试,正则才真正可用。









