正则表达式删除单行 SQL 注释 (--)

Posted

技术标签:

【中文标题】正则表达式删除单行 SQL 注释 (--)【英文标题】:Regex to remove single-line SQL comments (--) 【发布时间】:2012-04-08 05:41:02 【问题描述】:

问题:

谁能给我一个可以从 SQL 语句中删除单行 cmets 的有效正则表达式 (C#/VB.NET) 吗?

我的意思是这些cmets:

-- This is a comment

不是那些

/* this is a comment */

因为我已经可以处理明星cmets了。

我制作了一个小解析器,可以在它们位于行首时删除这些 cmets,但它们也可以在代码之后的某个地方或更糟,在 SQL 字符串 'hello --Test -- World' 中 那些 cmets 也应该被删除(当然除了那些在 SQL 字符串中的 - 如果可能的话)。

令人惊讶的是,我没有让正则表达式工作。我本以为明星 cmets 更难,但实际上并非如此。

根据要求,这里是我删除 /**/ 样式 cmets 的代码 (为了让它忽略 SQL 风格的字符串,你必须用唯一标识符替换字符串(我使用了 4 个连接),然后应用注释删除,然后应用字符串回补。

    static string RemoveCstyleComments(string strInput) 
     
        string strPattern = @"/[*][\w\d\s]+[*]/"; 
        //strPattern = @"/\*.*?\*/"; // Doesn't work 
        //strPattern = "/\\*.*?\\*/"; // Doesn't work 
        //strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work 
        //strPattern = @"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/ "; // Doesn't work 

        // http://***.com/questions/462843/improving-fixing-a-regex-for-c-style-block-comments 
        strPattern = @"/\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/";  // Works ! 

        string strOutput = System.Text.RegularExpressions.Regex.Replace(strInput, strPattern, string.Empty, System.Text.RegularExpressions.RegexOptions.Multiline); 
        Console.WriteLine(strOutput); 
        return strOutput; 
     // End Function RemoveCstyleComments 

【问题讨论】:

这里的最终目标是什么?解析语言有更好的工具... 您是如何处理字符串中的/**/ cmets 的? Austin Salonen:用一个(实际上是 4 个)唯一标识符替换字符串,然后删除 cmets,然后用回替换字符串。 没有字符串的正则表达式相当简单(请参阅每个答案) 正则表达式很有趣,但我认为在这种情况下,自己解析字符串会容易得多,除非有特定原因需要正则表达式。 【参考方案1】:

我会让你们所有人失望。这不能用正则表达式来完成。当然,很容易找到不在字符串中的 cmets(即使是 OP 也可以做到),真正的交易是字符串中的 cmets。 look arounds 有一点希望,但这还不够。通过告诉您在一行中有一个前面的引号并不能保证任何事情。唯一能保证你的东西是奇怪的引号。用正则表达式找不到的东西。所以只需简单地使用非正则表达式方法。

编辑: 这是c#代码:

        String sql = "--this is a test\r\nselect stuff where substaff like '--this comment should stay' --this should be removed\r\n";
        char[] quotes =  '\'', '"';
        int newCommentLiteral, lastCommentLiteral = 0;
        while ((newCommentLiteral = sql.IndexOf("--", lastCommentLiteral)) != -1)
        
            int countQuotes = sql.Substring(lastCommentLiteral, newCommentLiteral - lastCommentLiteral).Split(quotes).Length - 1;
            if (countQuotes % 2 == 0) //this is a comment, since there's an even number of quotes preceding
            
                int eol = sql.IndexOf("\r\n") + 2;
                if (eol == -1)
                    eol = sql.Length; //no more newline, meaning end of the string
                sql = sql.Remove(newCommentLiteral, eol - newCommentLiteral);
                lastCommentLiteral = newCommentLiteral;
            
            else //this is within a string, find string ending and moving to it
            
                int singleQuote = sql.IndexOf("'", newCommentLiteral);
                if (singleQuote == -1)
                    singleQuote = sql.Length;
                int doubleQuote = sql.IndexOf('"', newCommentLiteral);
                if (doubleQuote == -1)
                    doubleQuote = sql.Length;

                lastCommentLiteral = Math.Min(singleQuote, doubleQuote) + 1;

                //instead of finding the end of the string you could simply do += 2 but the program will become slightly slower
            
        

        Console.WriteLine(sql);

这是做什么的:查找每个注释文字。对于每一个,通过计算当前匹配和最后一个匹配之间的引号数量来检查它是否在评论中。如果这个数字是偶数,那么它是一个注释,因此删除它(找到第一个行尾并删除中间的内容)。如果它很奇怪,它在一个字符串中,找到字符串的结尾并移动到它。 Rgis sn-p 基于一个奇怪的 SQL 技巧:'this" 是一个有效的字符串。即使两个引号不同。如果您的 SQL 语言不正确,您应该尝试完全不同的方法. 如果是这样的话,我也会为此编写一个程序,但这个更快更直接。

【讨论】:

我想这可以使用正则表达式来完成。请在此处查看我的答案:***.com/a/33947706/3606250 任何复制粘贴的人都知道,这会失败:string sql = "SELECT 123 as abc, 'Hello foo /*bar*/ my --world ' AS xyz --Hello"; 【参考方案2】:

你想要这样的简单案例

-2,.*

-2, 查找出现 2 次或更多次的破折号

.* 将剩余的行放到换行符之前

*但是,对于边缘情况,SinistraD 似乎是正确的,因为您无法捕获所有内容,但是 here is an article 关于如何在 C# 中结合代码和正则表达式完成此操作。

【讨论】:

我认为这不会让 'hello --Test -- World' 孤单。 @Kramii 现在已修复。我错过了。对于投反对票的人,如果您仍在寻找,请重新查看:) 关闭,但您错过了字符串中出现 '' 的情况。例如,“WHERE name LIKE ' '' -- 这不是评论,但也会被匹配”。 @Kramii 这是由于间距。我相信现在应该很好了。它现在至少对你的例子有效 对不起! WHERE name LIKE ' ''x-- 这不是评论,但也会匹配'。【参考方案3】:

到目前为止,这对我来说似乎很有效;它甚至会忽略字符串中的 cmets,such as SELECT '--not a comment--' FROM ATable

    private static string removeComments(string sql)
    
        string pattern = @"(?<=^ ([^'""] |['][^']*['] |[""][^""]*[""])*) (--.*$|/\*(.|\n)*?\*/)";
        return Regex.Replace(sql, pattern, "", RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
    

注意:它旨在消除 /**/ 样式的 cmets 以及 -- 样式。删除 |/\*(.|\n)*?\*/ 以摆脱 /**/ 检查。还要确定您使用的是RegexOptions.IgnorePatternWhitespace Regex 选项!!

我也希望能够处理双引号,但由于 T-SQL 不支持它们,你也可以去掉 |[""][^""]*[""]

改编自here。

注意(2015 年 3 月):最后,我为这个项目使用了解析器生成器 Antlr。可能存在一些正则表达式不起作用的边缘情况。最后,我对使用 Antlr 的结果更有信心,而且效果很好。

【讨论】:

【参考方案4】:
Using System.Text.RegularExpressions;

public static string RemoveSQLCommentCallback(Match SQLLineMatch)

    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    bool open = false; //opening of SQL String found
    char prev_ch = ' ';

    foreach (char ch in SQLLineMatch.ToString())
    
        if (ch == '\'')
        
            open = !open;
        
        else if ((!open && prev_ch == '-' && ch == '-'))
        
            break;
        
        sb.Append(ch);
        prev_ch = ch;
    

    return sb.ToString().Trim('-');

代码

public static void Main()

    string sqlText = "WHERE DEPT_NAME LIKE '--Test--' AND START_DATE < SYSDATE -- Don't go over today";
    //for every matching line call callback func
    string result = Regex.Replace(sqlText, ".*--.*", RemoveSQLCommentCallback);

让我们替换,找到与破折号注释匹配的所有行,并为每个匹配项调用您的解析函数。

【讨论】:

【参考方案5】:

我不知道 C#/VB.net 正则表达式在某些方面是否特别,但传统上 s/--.*// 应该可以工作。

【讨论】:

不,这不会区分字符串文字内部和外部的'--'【参考方案6】:

php 中,我使用此代码取消注释 SQL(仅单行):

$sqlComments = '@(([\'"`]).*?[^\\\]\2)|((?:\#|--).*?$)\s*|(?<=;)\s+@ms';
/* Commented version
$sqlComments = '@
    (([\'"`]).*?[^\\\]\2) # $1 : Skip single & double quoted + backticked expressions
    |((?:\#|--).*?$)      # $3 : Match single line comments
    \s*                   # Trim after comments
    |(?<=;)\s+            # Trim after semi-colon
    @msx';
*/
$uncommentedSQL = trim( preg_replace( $sqlComments, '$1', $sql ) );
preg_match_all( $sqlComments, $sql, $comments );
$extractedComments = array_filter( $comments[ 3 ] );
var_dump( $uncommentedSQL, $extractedComments );

要删除所有 cmets,请参阅 Regex to match mysql comments

【讨论】:

【参考方案7】:

作为后期解决方案,最简单的方法是使用ScriptDom-TSqlParser:

// https://michaeljswart.com/2014/04/removing-comments-from-sql/
// http://web.archive.org/web/*/https://michaeljswart.com/2014/04/removing-comments-from-sql/
public static string StripCommentsFromSQL(string SQL)

    Microsoft.SqlServer.TransactSql.ScriptDom.TSql150Parser parser = 
        new Microsoft.SqlServer.TransactSql.ScriptDom.TSql150Parser(true);

    System.Collections.Generic.IList<Microsoft.SqlServer.TransactSql.ScriptDom.ParseError> errors;


    Microsoft.SqlServer.TransactSql.ScriptDom.TSqlFragment fragments = 
        parser.Parse(new System.IO.StringReader(SQL), out errors);

    // clear comments
    string result = string.Join(
      string.Empty,
      fragments.ScriptTokenStream
          .Where(x => x.TokenType != Microsoft.SqlServer.TransactSql.ScriptDom.TSqlTokenType.MultilineComment)
          .Where(x => x.TokenType != Microsoft.SqlServer.TransactSql.ScriptDom.TSqlTokenType.SingleLineComment)
          .Select(x => x.Text));

    return result;


或者不使用 Microsoft-Parser,您可以使用 ANTL4 TSqlLexer

或者根本没有任何解析器:

private static System.Text.RegularExpressions.Regex everythingExceptNewLines = 
    new System.Text.RegularExpressions.Regex("[^\r\n]");


// http://drizin.io/Removing-comments-from-SQL-scripts/
// http://web.archive.org/web/*/http://drizin.io/Removing-comments-from-SQL-scripts/
public static string RemoveComments(string input, bool preservePositions, bool removeLiterals = false)

    //based on http://***.com/questions/3524317/regex-to-strip-line-comments-from-c-sharp/3524689#3524689
    var lineComments = @"--(.*?)\r?\n";
    var lineCommentsOnLastLine = @"--(.*?)$"; // because it's possible that there's no \r\n after the last line comment
                                              // literals ('literals'), bracketedIdentifiers ([object]) and quotedIdentifiers ("object"), they follow the same structure:
                                              // there's the start character, any consecutive pairs of closing characters are considered part of the literal/identifier, and then comes the closing character
    var literals = @"('(('')|[^'])*')"; // 'John', 'O''malley''s', etc
    var bracketedIdentifiers = @"\[((\]\])|[^\]])* \]"; // [object], [ % object]] ], etc
    var quotedIdentifiers = @"(\""((\""\"")|[^""])*\"")"; // "object", "object[]", etc - when QUOTED_IDENTIFIER is set to ON, they are identifiers, else they are literals
                                                          //var blockComments = @"/\*(.*?)\*/";  //the original code was for C#, but Microsoft SQL allows a nested block comments // //https://msdn.microsoft.com/en-us/library/ms178623.aspx

    //so we should use balancing groups // http://weblogs.asp.net/whaggard/377025
    var nestedBlockComments = @"/\*
                         (?>
                         /\*  (?<LEVEL>)      # On opening push level
                         | 
                         \*/ (?<-LEVEL>)     # On closing pop level
                         |
                         (?! /\* | \*/ ) . # Match any char unless the opening and closing strings   
                         )+                         # /* or */ in the lookahead string
                         (?(LEVEL)(?!))             # If level exists then fail
                         \*/";

    string noComments = System.Text.RegularExpressions.Regex.Replace(input,
        nestedBlockComments + "|" + lineComments + "|" + lineCommentsOnLastLine + "|" + literals + "|" + bracketedIdentifiers + "|" + quotedIdentifiers,
        me => 
            if (me.Value.StartsWith("/*") && preservePositions)
                return everythingExceptNewLines.Replace(me.Value, " "); // preserve positions and keep line-breaks // return new string(' ', me.Value.Length);
     else if (me.Value.StartsWith("/*") && !preservePositions)
                return "";
            else if (me.Value.StartsWith("--") && preservePositions)
                return everythingExceptNewLines.Replace(me.Value, " "); // preserve positions and keep line-breaks
     else if (me.Value.StartsWith("--") && !preservePositions)
                return everythingExceptNewLines.Replace(me.Value, ""); // preserve only line-breaks // Environment.NewLine;
     else if (me.Value.StartsWith("[") || me.Value.StartsWith("\""))
                return me.Value; // do not remove object identifiers ever
     else if (!removeLiterals) // Keep the literal strings
         return me.Value;
            else if (removeLiterals && preservePositions) // remove literals, but preserving positions and line-breaks
     
                var literalWithLineBreaks = everythingExceptNewLines.Replace(me.Value, " ");
                return "'" + literalWithLineBreaks.Substring(1, literalWithLineBreaks.Length - 2) + "'";
            
            else if (removeLiterals && !preservePositions) // wrap completely all literals
         return "''";
            else
                throw new System.NotImplementedException();
        ,
        System.Text.RegularExpressions.RegexOptions.Singleline | System.Text.RegularExpressions.RegexOptions.IgnorePatternWhitespace);
    return noComments;

【讨论】:

以上是关于正则表达式删除单行 SQL 注释 (--)的主要内容,如果未能解决你的问题,请参考以下文章

用于匹配单行和多行注释的 Python 正则表达式。

正则表达式匹配注释(单行或多行)并匹配其他所有内容(C#)

注释的正则表达式,但不在“字符串”内/不在另一个容器中

如何删除 php 中的单行注释(例如“// 删除此注释”)?

eclipsemyeclipse中删除所有注释正则表达式

正则表达式删除多行注释