正则表达式:匹配不包含指定字符串的文本

在日常的文本处理、数据筛选场景中,我们经常需要反向匹配------找出不包含某个特定字符串的文本内容。正则表达式本身擅长"匹配存在的内容",但通过"负向环视"特性,我们可以轻松实现这一反向需求。

原理:负向环视(Negative Lookahead)

要匹配不包含指定字符串(如 target)的文本,核心正则表达式如下:

复制代码
^(?!.*target).*$

表达式逐部分解析

表达式片段 具体含义
^ 匹配字符串的开头位置,确保从文本最起始处开始检查
(?!.*target) 负向环视(负向前瞻断言): - ?! 表示"断言当前位置后不存在指定模式"; - .* 匹配任意字符(除换行符外,单行模式除外)任意次数; - 整体作用:从开头位置检查,确保后续所有内容中都找不到 target
.* 匹配符合"不包含 target"条件的任意字符(除换行符)任意次数,即文本主体内容
$ 匹配字符串的结尾位置,确保检查覆盖到文本末尾

效果:该表达式仅匹配"从开头到结尾完整、且全程不包含 target"的字符串,只要文本中出现 target,匹配立即失败。

实例演示

PHP 示例

php 复制代码
<?php
// 待处理的多行文本(包含换行的跨行文内容)
$text = "
hello world
test target 123
php regex demo
这是跨换行的内容
foo target bar
no match here
";

// 场景1:仅按行匹配(不跨换行)
echo "=== 场景1:仅按行匹配(不跨换行)===\n";
$regex1 = '/^(?!.*target).*$/m';
preg_match_all($regex1, $text, $matches1);
foreach ($matches1[0] as $match) {
    $match = trim($match);
    if ($match !== "") {
        echo $match . "\n";
    }
}

// 场景2:跨换行匹配整段内容(/s 开启单行模式)
echo "\n=== 场景2:跨换行匹配整段内容 ===\n";
$regex2 = '/^(?!.*target).*$/s';
// 用 preg_match 匹配整段,而非 preg_match_all
$isFullMatch = preg_match($regex2, $text);
if (!$isFullMatch) {
    echo "整段文本包含 target,匹配失败\n";
}

// 测试无 target 的跨行文本
$textNoTarget = "
hello php
这是跨换行的内容
无 target 字符串
";
$isNoTargetMatch = preg_match($regex2, $textNoTarget);
if ($isNoTargetMatch) {
    echo "\n无 target 的跨行文本匹配结果:\n";
    echo trim($textNoTarget) . "\n";
}
?>

运行结果

复制代码
=== 场景1:仅按行匹配(不跨换行)===
hello world
php regex demo
这是跨换行的内容
no match here

=== 场景2:跨换行匹配整段内容 ===
整段文本包含 target,匹配失败

无 target 的跨行文本匹配结果:
hello php
这是跨换行的内容
无 target 字符串

Java 示例

java 复制代码
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexNoTarget {
    public static void main(String[] args) {
        // 待处理的多行文本(包含换行的跨行文内容)
        String text = """
                hello world
                test target 123
                java regex demo
                这是跨换行的内容
                foo target bar
                no match here
                """;
        
        // 场景1:仅按行匹配(不跨换行)
        System.out.println("=== 场景1:仅按行匹配(不跨换行)===");
        String regex1 = "^(?!.*target).*$";
        Pattern pattern1 = Pattern.compile(regex1, Pattern.MULTILINE);
        Matcher matcher1 = pattern1.matcher(text);
        while (matcher1.find()) {
            String match = matcher1.group().trim();
            if (!match.isEmpty()) {
                System.out.println(match);
            }
        }

        // 场景2:跨换行匹配整段内容(开启 DOTALL)
        System.out.println("\n=== 场景2:跨换行匹配整段内容 ===");
        String regex2 = "^(?!.*target).*$";
        Pattern pattern2 = Pattern.compile(regex2, Pattern.DOTALL);
        Matcher matcher2 = pattern2.matcher(text);
        if (matcher2.matches()) {
            System.out.println(matcher2.group());
        } else {
            System.out.println("整段文本包含 target,匹配失败");
        }

        // 测试无 target 的跨行文本
        String textNoTarget = """
                hello java
                这是跨换行的内容
                无 target 字符串
                """;
        Matcher matcher3 = pattern2.matcher(textNoTarget);
        if (matcher3.matches()) {
            System.out.println("\n无 target 的跨行文本匹配结果:");
            System.out.println(matcher3.group().trim());
        }
    }
}

运行结果

复制代码
=== 场景1:仅按行匹配(不跨换行)===
hello world
java regex demo
这是跨换行的内容
no match here

=== 场景2:跨换行匹配整段内容 ===
整段文本包含 target,匹配失败

无 target 的跨行文本匹配结果:
hello java
这是跨换行的内容
无 target 字符串

Go 示例

go 复制代码
package main

import (
	"fmt"
	"regexp"
	"strings"
)

func main() {
	// 待处理的多行文本(包含换行的跨行文内容)
	text := `
hello world
test target 123
golang regex demo
这是跨换行的内容
foo target bar
no match here
	`

	// 场景1:仅按行匹配(不跨换行)
	fmt.Println("=== 场景1:仅按行匹配(不跨换行)===")
	regex1 := regexp.MustCompile(`(?m)^(?!.*target).*$`)
	matches1 := regex1.FindAllString(text, -1)
	for _, match := range matches1 {
		match = strings.TrimSpace(match)
		if match != "" {
			fmt.Println(match)
		}
	}

	// 场景2:跨换行匹配整段内容((?s) 开启单行模式)
	fmt.Println("\n=== 场景2:跨换行匹配整段内容 ===")
	regex2 := regexp.MustCompile(`(?s)^(?!.*target).*$`)
	// 用 MatchString 判断整段是否匹配,更严谨
	isFullMatch := regex2.MatchString(text)
	if !isFullMatch {
		fmt.Println("整段文本包含 target,匹配失败")
	}

	// 测试无 target 的跨行文本
	textNoTarget := `
hello golang
这是跨换行的内容
无 target 字符串
	`
	isNoTargetMatch := regex2.MatchString(textNoTarget)
	if isNoTargetMatch {
		fmt.Println("\n无 target 的跨行文本匹配结果:")
		fmt.Println(strings.TrimSpace(textNoTarget))
	}
}

运行结果

复制代码
=== 场景1:仅按行匹配(不跨换行)===
hello world
golang regex demo
这是跨换行的内容
no match here

=== 场景2:跨换行匹配整段内容 ===
整段文本包含 target,匹配失败

无 target 的跨行文本匹配结果:
hello golang
这是跨换行的内容
无 target 字符串

JavaScript 示例

javascript 复制代码
// 待处理的多行文本
const text = `
hello world
test target 123
javascript regex demo
这是跨换行的内容
foo target bar
no match here
`;

// 无 target 的跨行测试文本
const textNoTarget = `
hello javascript
这是跨换行的内容
无 target 字符串
`;

// 场景1:仅按行匹配(开启多行模式 m)
console.log("=== 场景1:仅按行匹配(不跨换行)===");
const regex1 = /^(?!.*target).*$/m;
// 用 match 直接提取所有匹配行,更简洁
const matches1 = text.match(regex1) || [];
matches1.forEach(line => {
    const trimmed = line.trim();
    if (trimmed) {
        console.log(trimmed);
    }
});

// 场景2:跨换行匹配整段内容(开启单行模式 s)
console.log("\n=== 场景2:跨换行匹配整段内容 ===");
const regex2 = /^(?!.*target).*$/s;
if (!regex2.test(text)) {
    console.log("整段文本包含 target,匹配失败");
}

// 匹配无 target 的文本
if (regex2.test(textNoTarget)) {
    console.log("\n无 target 的跨行文本匹配结果:");
    console.log(textNoTarget.trim());
}

运行环境 :浏览器控制台/Node.js(v10+ 支持 s 修饰符)
运行结果

复制代码
=== 场景1:仅按行匹配(不跨换行)===
hello world
javascript regex demo
这是跨换行的内容
no match here

=== 场景2:跨换行匹配整段内容 ===
整段文本包含 target,匹配失败

无 target 的跨行文本匹配结果:
hello javascript
这是跨换行的内容
无 target 字符串

C++ 示例

cpp 复制代码
#include <iostream>
#include <regex>
#include <string>
#include <iterator>
#include <algorithm>

// 完善 trim 函数,处理全空白字符串
std::string trim(const std::string& str) {
    const std::string whitespace = " \t\n\r";
    const size_t start = str.find_first_not_of(whitespace);
    if (start == std::string::npos) return ""; // 全空白返回空
    const size_t end = str.find_last_not_of(whitespace);
    return str.substr(start, end - start + 1);
}

int main() {
    // 待处理的多行文本
    std::string text = 
        "\nhello world\n"
        "test target 123\n"
        "c++ regex demo\n"
        "这是跨换行的内容\n"
        "foo target bar\n"
        "no match here\n";
    
    // 无 target 的跨行测试文本
    std::string textNoTarget = 
        "\nhello c++\n"
        "这是跨换行的内容\n"
        "无 target 字符串\n";

    // 场景1:仅按行匹配(开启多行模式)
    std::cout << "=== 场景1:仅按行匹配(不跨换行)===" << std::endl;
    std::regex regex1("^(?!.*target).*$", std::regex_constants::multiline);
    std::sregex_iterator it1(text.begin(), text.end(), regex1);
    std::sregex_iterator end1;
    for (std::sregex_iterator i = it1; i != end1; ++i) {
        std::string match = (*i).str();
        std::string trimmed = trim(match);
        if (!trimmed.empty()) {
            std::cout << trimmed << std::endl;
        }
    }

    // 场景2:跨换行匹配整段内容(开启 dotall 模式)
    std::cout << "\n=== 场景2:跨换行匹配整段内容 ===" << std::endl;
    std::regex regex2("^(?!.*target).*$", std::regex_constants::dotall);
    bool isMatch = std::regex_match(text, regex2);
    if (!isMatch) {
        std::cout << "整段文本包含 target,匹配失败" << std::endl;
    }

    // 匹配无 target 的文本
    bool isMatchNoTarget = std::regex_match(textNoTarget, regex2);
    if (isMatchNoTarget) {
        std::cout << "\n无 target 的跨行文本匹配结果:" << std::endl;
        std::cout << trim(textNoTarget) << std::endl;
    }

    return 0;
}

编译运行命令

bash 复制代码
# 编译(需支持 C++11 及以上)
g++ regex_no_target.cpp -o regex_no_target -std=c++11
# 运行
./regex_no_target

运行结果

复制代码
=== 场景1:仅按行匹配(不跨换行)===
hello world
c++ regex demo
这是跨换行的内容
no match here

=== 场景2:跨换行匹配整段内容 ===
整段文本包含 target,匹配失败

无 target 的跨行文本匹配结果:
hello c++
这是跨换行的内容
无 target 字符串

Python 示例

python 复制代码
import re

def main():
    # 待处理的多行文本(包含换行的跨行文内容)
    text = """
hello world
test target 123
python regex demo
这是跨换行的内容
foo target bar
no match here
    """

    # 场景1:仅按行匹配(不跨换行)
    print("=== 场景1:仅按行匹配(不跨换行)===")
    regex1 = r'^(?!.*target).*$'
    # 开启多行模式 re.M
    matches1 = re.findall(regex1, text, re.M)
    for match in matches1:
        match = match.strip()
        if match:
            print(match)

    # 场景2:跨换行匹配整段内容(开启单行模式 re.S)
    print("\n=== 场景2:跨换行匹配整段内容 ===")
    regex2 = r'^(?!.*target).*$'
    # 仅开启单行模式,匹配整段文本
    is_full_match = re.fullmatch(regex2, text, re.S) is not None
    if not is_full_match:
        print("整段文本包含 target,匹配失败")

    # 测试无 target 的跨行文本
    text_no_target = """
hello python
这是跨换行的内容
无 target 字符串
    """
    is_no_target_match = re.fullmatch(regex2, text_no_target, re.S) is not None
    if is_no_target_match:
        print("\n无 target 的跨行文本匹配结果:")
        print(text_no_target.strip())

if __name__ == "__main__":
    main()

运行结果

复制代码
=== 场景1:仅按行匹配(不跨换行)===
hello world
python regex demo
这是跨换行的内容
no match here

=== 场景2:跨换行匹配整段内容 ===
整段文本包含 target,匹配失败

无 target 的跨行文本匹配结果:
hello python
这是跨换行的内容
无 target 字符串

扩展场景

1. 排除多个字符串

若需同时排除 target1 和 target2,只需叠加负向环视,核心模板如下:

复制代码
^(?!.*target1)(?!.*target2).*$
Python 示例(匹配不包含 foo 和 bar 的文本)
python 复制代码
import re
text = "test foo 123\nhello bar 456\npython demo 789"
regex = r'^(?!.*foo)(?!.*bar).*$'
matches = re.findall(regex, text, re.M)
print([m.strip() for m in matches if m.strip()])  # 输出:['python demo 789']
PHP 示例(匹配不包含 foo 和 bar 的文本)
php 复制代码
<?php
$text = "test foo 123\nhello bar 456\nphp demo 789";
$regex = '/^(?!.*foo)(?!.*bar).*$/m';
preg_match_all($regex, $text, $matches);
$result = array_filter(array_map('trim', $matches[0]));
print_r($result); // 输出:Array ( [2] => php demo 789 )
?>

运行结果

复制代码
Array
(
    [2] => php demo 789
)
Java 示例(匹配不包含 foo 和 bar 的文本)
java 复制代码
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexExcludeMulti {
    public static void main(String[] args) {
        String text = "test foo 123\nhello bar 456\njava demo 789";
        String regex = "^(?!.*foo)(?!.*bar).*$";
        Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(text);
        
        while (matcher.find()) {
            String match = matcher.group().trim();
            if (!match.isEmpty()) {
                System.out.println(match); // 输出:java demo 789
            }
        }
    }
}

运行结果

复制代码
java demo 789
Go 示例(匹配不包含 foo 和 bar 的文本)
go 复制代码
package main

import (
	"fmt"
	"regexp"
	"strings"
)

func main() {
	text := "test foo 123\nhello bar 456\ngolang demo 789"
	regex := regexp.MustCompile(`(?m)^(?!.*foo)(?!.*bar).*$`)
	matches := regex.FindAllString(text, -1)
	
	var result []string
	for _, match := range matches {
		trimmed := strings.TrimSpace(match)
		if trimmed != "" {
			result = append(result, trimmed)
		}
	}
	fmt.Println(result) // 输出:[golang demo 789]
}

运行结果

复制代码
[golang demo 789]

补充说明

  1. 排除多个字符串的核心逻辑是叠加多个负向环视断言 ,每个 (?!.*目标字符串) 对应一个需要排除的内容;
  2. 多语言实现时仅需保持正则核心不变,调整模式(如多行模式 m/(?m)/Pattern.MULTILINE)和匹配函数即可;
  3. 若需排除更多字符串(如 target3、target4),继续叠加 (?!.*target3)(?!.*target4) 即可,逻辑可无限扩展。

2. 同时开启多行+单行模式

若需按行匹配跨换行的文本(比如每行内容本身包含换行符),可同时开启两种模式:

语言 模式开启方式(同时开启多行+单行) 示例正则
PHP /ms /^(?!.target).$/ms
Java Pattern.MULTILINE | Pattern.DOTALL Pattern.compile("^(?!.target).$", Pattern.MULTILINE | Pattern.DOTALL)
Go (?ms) regexp.MustCompile(`(?ms)^(?!.target).$`)
JavaScript /ms /^(?!.target).$/ms
C++ std::regex_constants::multiline | std::regex_constants::dotall std::regex("^(?!.target).$", std::regex_constants::multiline | std::regex_constants::dotall)
Python re.M | re.S re.findall(r'^(?!.target).$', text, re.M | re.S)

总结

  1. 匹配"不包含指定字符串"的核心是负向环视 (?!...),基础模板为 ^(?!.*目标字符串).*$
  2. . 匹配换行符需开启单行模式:PHP /s、Java Pattern.DOTALL、Golang (?s)、JavaScript /s、C++ dotall、Python re.S;
  3. 多语言按行匹配需开启多行模式:PHP /m、Java Pattern.MULTILINE、Golang (?m)、JavaScript /m、C++ multiline、Python re.M;
  4. 排除多个字符串时,叠加多个负向环视断言即可,逻辑简单且易扩展;
  5. 不同语言的正则语法和标志略有差异,但核心原理相通,掌握一种后可快速迁移到其他语言。
相关推荐
Hello.Reader1 小时前
Nuxt 4.2 + Tauri 2 接入指南把 Vue 元框架“静态化”后装进桌面/移动端
前端·javascript·vue.js
独隅2 小时前
macOS 查看与安装 Java JDK 全面指南(2026年版)
java·开发语言·macos
西门吹雪分身2 小时前
SpringCloudGateway过滤器之RequestRateLimiterGatewayFilterFactory
java·redis·spring cloud
敲代码的哈吉蜂2 小时前
Tomcat的功能介绍
java·tomcat
独自破碎E2 小时前
BISHI75 阶幂
android·java·开发语言
红中️2 小时前
Tomcat
java·tomcat
pas1362 小时前
47-mini-vue 升级monorepo管理项目
前端·javascript·vue.js
爱学习的小可爱卢2 小时前
JavaSE基础-Java异常体系:Bug定位终极指南
java·bug·javase
浮桥2 小时前
uniapp + h5 -- 简易抽奖转盘组件(文字版)
前端·javascript·uni-app