正则表达式 - 删除 space 连续多个字符,但排除所有注释掉的行

Regex - Remove space chars whenever there are more than one in succession, but exclude all lines commented out

假设我有几行如下:

01090   C   -------CALCULATION OF SOMETHING--
01100   "SOME.VARIABLE"   =  "SOME.OTHER.VARIABLE" + 2
01110   IF("SOME.VARIABLE" .NE.  "SOME.VALUE")  THEN   ON("SOME.MACHINE")

我想通过程序删除所有 space 个以上连续的字符。例如,第 01100 行在“=”之前有三 (3) 个 space 个字符,在“=”之后有两 (2) 个字符。在第 01110 行中,有几个不同的位置具有超过 1 个连续的 space 个字符。 我想用一个 space 字符替换它们。我不想 remove/alter 注释行 01090 中包含的 space。

所有行都以 5 位数字开头,所有行的行号后面都有一个制表符,只有注释行有“C”或“c”表示它们已被注释掉。

我正在使用 Sublime3 和 boost 正则表达式。我尝试过类似的东西:

(?!\t[Cc] )[ ]{2,}
(?!\t[Cc])[ ]{2,}

我似乎无法确定如何在不捕获整行的情况下否定整行。

我也试过在开头放一个插入符号,但这似乎没有帮助。

基本上,如果该行有一个“TAB”后跟一个“c”或“C”,则忽略整个内容。否则,任何两个或多个连续的 space 字符被定位并替换为单个 space 字符。

编辑

--------解决方案--------

感谢 Wiktor 和 The fourth bird 的输入,我能够确定解决方案。非常感谢他们。这是我最终得到的结果:

^\d+\t[cC].*\K|[ ]{2,}

我还确定,如果行尾有额外的 space,我可能也想忽略它们,以便我可以使用不同的正则表达式搜索完全删除它们。最终产品如下所示:

^\d+\t[cC].*\K|[ ]*\n\K|[ ]{2,}

如果不是受限于boost或者PCRE的引擎,我相信我之前失败的一次尝试真的能成功。如果它能帮助别人,我会把它包括在这里。它不能用于 boost 或 PCRE,因为它们不支持无限后视:

(?<!\t[cC].*)[ ]{2,}

您必须向您的正则表达式添加否定前瞻和否定后视。尝试这样的事情。

(?<![Cc])\s{3,}(?![Cc])

您可能会使用

^\d{5}\t[cC] .*$(*SKIP)(*FAIL)|\h{2,}
  • ^ 字符串开头
  • \d{5}\t 匹配 5 个数字和一个制表符
  • [cC] 匹配 cC 和 space
  • .*$ 匹配行的其余部分
  • (*SKIP)(*FAIL) 跳过比赛
  • |
  • \h{2,} 匹配 2 个或更多水平白色space 个字符

在替换中使用单个 space。

Regex demo

正如 OP 所解决的,使用 \K 并匹配 2 个或更多 spaces 的缩短版本:

^\d+\tC.*\K|[ ]{2,} 
  • ^ 字符串开头
  • \d+匹配1个或多个数字
  • \tC 匹配制表符和 C 字符
  • .*\K 匹配行的其余部分并清除匹配缓冲区
  • |
  • [ ]{2,} 匹配2个或更多space(方括号只是为了可见,不是必需的)

Regex demo

我认为您实际上可能正在准备解析这种语言。解析器通常不方便使用正则表达式。

此外,您没有询问,但在这种情况下,转换可能是 in-place(因为输出比输入短,或者长度相等)。

我建议使用这样的 PEG 语法(使用 Boost Spirit):

template <typename In, typename Out>
Out compress_whitespace(In f, In l, Out out) {
    auto copy = [&out](auto& ctx) {
        struct Append {
            static void call(Out& out, char ch) { *out++ = ch; }
            static void call(Out& out, boost::iterator_range<In> raw) {
                for (auto ch : raw) *out++ = ch; }
        };

        Append::call(out, _attr(ctx));
    };

    using namespace boost::spirit::x3;
    auto prefix  = raw[uint_ >> "   "][copy];
    auto comment = raw["C " >> *(char_ - eol)][copy];
    auto code_ch = omit[+blank] >> attr(' ')[copy] | (char_ - eol)[copy];
    auto line    = prefix >> (comment | *code_ch);
    auto newline = raw[eol][copy];

    parse(f, l, -line % newline);
    return out;
}

禁止空行:

    parse(f, l, line % newline);

要抛出 incomplete/invalid 输入,请更改 parse 行:

    parse(f, l, expect[line % newline >> *newline >> eoi]);

Live Demo

int main(int argc, char** argv)
{
    std::ostreambuf_iterator out(std::cout);

    for (std::string file : std::vector(argv+1, argv+argc)) {
        std::ifstream s(file, std::ios::binary);
        std::string const program(std::istreambuf_iterator<char>{s}, {});

        compress_whitespace(begin(program), end(program), out);
    }
}

输出使用vim -d input.txt <(./sotest input.txt)

奖励:就地处理

由于我们知道输出的长度相同或更短,因此您可以就地处理:

    std::string program = R"~(
01090   C   -------CALCULATION OF SOMETHING--
01100   "SOME.VARIABLE"   =  "SOME.OTHER.VARIABLE" + 2
01110   IF("SOME.VARIABLE" .NE.  "SOME.VALUE")  THEN   ON("SOME.MACHINE"))~";

    auto b = begin(program), e = end(program),
         new_e = compress_whitespace(b, e, b);

    std::cout << "Shorter by " << (e - new_e) << " chars\n";
    program.erase(new_e, e);

    std::cout << program << "\n";

看到了Live On Coliru,打印:

Shorter by 7 chars

01090   C   -------CALCULATION OF SOMETHING--
01100   "SOME.VARIABLE" = "SOME.OTHER.VARIABLE" + 2
01110   IF("SOME.VARIABLE" .NE. "SOME.VALUE") THEN ON("SOME.MACHINE")