【问题标题】:Capture strings in quotes as single command argument将引号中的字符串捕获为单个命令参数
【发布时间】:2018-11-13 06:16:58
【问题描述】:

我正在尝试制作一个与服务器进行一些交互的 Discord 机器人。

我已经编写了一些可以工作的代码,但它存在一个大问题。这是我的代码:

if (command === "file") {

        var accusor = message.author.id;
        var username = args[0];
        var reason = args[1];
        var punishment = args[2];
        var duration = args[3];
        if(!duration) duration = "N/A";
        console.log("Returning last " + amount + " for " + username);
        request.post({url:'http://grumpycrouton.com/kismet/api/post_complaint.php', form: {accusor:accusor,search:username,reason:reason,punishment:punishment,duration:duration}}, function(err,httpResponse,body) { 
            message.reply(body); 
        });
    }

命令是!file {playername} {reason} {punishment} {duration},但问题是,有时一些变量可能有多个单词。例如,{reason} 可能类似于“玩家过得不好”,但由于参数的拆分方式,我的代码无法正确解析。

假设输入了这个命令:

!file GrumpyCrouton "Player had a bad time" Kick "1 Day" 但是参数实际上会以不同的方式展开,因为第三个参数中有空格,但是正则表达式将所有参数都用空格分割,而不管引号如何。基本上,Discord 会忽略引号并将每个单词用作它自己的参数,从而使 {punishment}{duration} 的参数索引为 6 和 7 而不是 2 和 3,因为每个单词都被视为一个参数。

这是我的论点的解读方式:

const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();

我怎样才能使括在引号中的字符串被读取为单个参数而不是多个参数?

【问题讨论】:

    标签: node.js discord.js


    【解决方案1】:

    一个简单的正则表达式就可以解决问题:)

    const input = 'ban user "Look at what you have done!" 7d "This is another string" value';
    const regex = new RegExp('"[^"]+"|[\\S]+', 'g');
    const arguments = [];
    input.match(regex).forEach(element => {
        if (!element) return;
        return arguments.push(element.replace(/"/g, ''));
    });
    console.log(arguments);
    
    /*
     * Use a function with a spreader like:
     * doSomething(...arguments);
     */
    

    【讨论】:

    • 只是想感谢您如此高效简洁的回答。
    【解决方案2】:

    我之所以遇到这个问题,是因为我对 OP 有类似的要求(解析可能包含带有嵌入空格的双引号参数的字符串)。但是,接受的答案并没有满足我的需要(它去除了空格,并且对参数的数量做出了过多的假设)。因此,我必须设计自己的解决方案,并在此提供,以防其他人发现它有用。

    实际上有两种变体:第一种不允许允许双引号出现在生成的参数列表中;第二个确实通过在双引号字符串中使用双双引号(...""...)来允许这样做。 (我实际上是先写了这个版本,“因为 Windows 下的 Node 就是这样做的”,然后将其删减为第一个变体。

    在这两个示例中,log() 函数以及从 splitCommandLine() 中对其进行的调用纯粹是为了展示内部工作原理,可以省略。


    简单的双引号字符串

    • 参数通常会以空格分隔。
    • 双引号字符串被视为一个参数,即使它们包含空格。
    • 双引号内的多个空格将被保留。
    • 多个空格双引号之外,它们将被视为一个空格。
    • 如果缺少最后的结束双引号,则会假定它。
    • 不能将双引号字符放入参数中。
    splitCommandLine( 'param1   "   param   2" param3 "param  4  " "param 5' ) ;
    
    log( 'argv', process.argv.slice(2) ) ;
    
    function log( n, v ) {
        console.log( n ) ;
        console.dir( v ) ;
        console.log() ;
    }
    
    function splitCommandLine( commandLine ) {
    
        log( 'commandLine', commandLine ) ;
    
        //  Find a unique marker for the space character.
        //  Start with '<SP>' and repeatedly append '@' if necessary to make it unique.
        var spaceMarker = '<SP>' ;
        while( commandLine.indexOf( spaceMarker ) > -1 ) spaceMarker += '@' ;
    
        //  Protect double-quoted strings.
        //   o  Find strings of non-double-quotes, wrapped in double-quotes.
        //   o  The final double-quote is optional to allow for an unterminated string.
        //   o  Replace each double-quoted-string with what's inside the qouble-quotes,
        //      after each space character has been replaced with the space-marker above.
        //   o  The outer double-quotes will not be present.
        var noSpacesInQuotes = commandLine.replace( /"([^"]*)"?/g, ( fullMatch, capture ) => {
            return capture.replace( / /g, spaceMarker ) ;
        }) ;
    
        log( 'noSpacesInQuotes', noSpacesInQuotes ) ;
    
        //  Now that it is safe to do so, split the command-line at one-or-more spaces.
        var mangledParamArray = noSpacesInQuotes.split( / +/ ) ;
    
        log( 'mangledParamArray', mangledParamArray ) ;
    
        //  Create a new array by restoring spaces from any space-markers.
        var paramArray = mangledParamArray.map( ( mangledParam ) => {
            return mangledParam.replace( RegExp( spaceMarker, 'g' ), ' ' ) ;
        });
    
        log( 'paramArray', paramArray ) ;
    
        return paramArray ;
    }
    

    使用嵌入在代码中的相同命令行运行它表明它产生与 Node/Windows 命令行解析器相同的输出:

    C:\>node test1.js param1   "   param   2" param3 "param  4  " "param 5
    commandLine
    'param1   "   param   2" param3 "param  4  " "param 5'
    
    noSpacesInQuotes
    'param1   <SP><SP><SP>param<SP><SP><SP>2 param3 param<SP><SP>4<SP><SP> param<SP>5'
    
    mangledParamArray
    [ 'param1',
      '<SP><SP><SP>param<SP><SP><SP>2',
      'param3',
      'param<SP><SP>4<SP><SP>',
      'param<SP>5' ]
    
    paramArray
    [ 'param1', '   param   2', 'param3', 'param  4  ', 'param 5' ]
    
    argv
    [ 'param1', '   param   2', 'param3', 'param  4  ', 'param 5' ]
    

    带有双双引号的双引号字符串

    • 与第一个示例完全相同,除了在双引号字符串中,双双引号 (..."aaa ""bbb"" ccc"...) 将在解析参数 ( aaa "bbb" ccc)。在双引号字符串之外,将忽略双双引号。这模仿了 Windows 下的 Node 如何解析命令行(未在 Unix 变体上测试)。
    splitCommandLine( 'param1   "   param   2" param""3 "param "" 4  " "param 5' ) ;
    
    log( 'argv', process.argv.slice(2) ) ;
    
    function log( n, v ) {
        console.log( n ) ;
        console.dir( v ) ;
        console.log() ;
    }
    
    function splitCommandLine( commandLine ) {
    
        log( 'commandLine', commandLine ) ;
    
        //  Find a unique marker for pairs of double-quote characters.
        //  Start with '<DDQ>' and repeatedly append '@' if necessary to make it unique.
        var doubleDoubleQuote = '<DDQ>' ;
        while( commandLine.indexOf( doubleDoubleQuote ) > -1 ) doubleDoubleQuote += '@' ;
    
        //  Replace all pairs of double-quotes with above marker.
        var noDoubleDoubleQuotes = commandLine.replace( /""/g, doubleDoubleQuote ) ;
    
        log( 'noDoubleDoubleQuotes', noDoubleDoubleQuotes ) ;
    
        //  As above, find a unique marker for spaces.
        var spaceMarker = '<SP>' ;
        while( commandLine.indexOf( spaceMarker ) > -1 ) spaceMarker += '@' ;
    
        //  Protect double-quoted strings.
        //   o  Find strings of non-double-quotes, wrapped in double-quotes.
        //   o  The final double-quote is optional to allow for an unterminated string.
        //   o  Replace each double-quoted-string with what's inside the qouble-quotes,
        //      after each space character has been replaced with the space-marker above;
        //      and each double-double-quote marker has been replaced with a double-
        //      quote character.
        //   o  The outer double-quotes will not be present.
        var noSpacesInQuotes = noDoubleDoubleQuotes.replace( /"([^"]*)"?/g, ( fullMatch, capture ) => {
            return capture.replace( / /g, spaceMarker )
                          .replace( RegExp( doubleDoubleQuote, 'g' ), '"' ) ;
        }) ;
    
        log( 'noSpacesInQuotes', noSpacesInQuotes ) ;
    
        //  Now that it is safe to do so, split the command-line at one-or-more spaces.
        var mangledParamArray = noSpacesInQuotes.split( / +/ ) ;
    
        log( 'mangledParamArray', mangledParamArray ) ;
    
        //  Create a new array by restoring spaces from any space-markers. Also, any
        //  remaining double-double-quote markers must have been from OUTSIDE a double-
        //  quoted string and so are removed.
        var paramArray = mangledParamArray.map( ( mangledParam ) => {
            return mangledParam.replace( RegExp( spaceMarker,       'g' ), ' ' )
                               .replace( RegExp( doubleDoubleQuote, 'g' ), ''  ) ;
        });
    
        log( 'paramArray', paramArray ) ;
    
        return paramArray ;
    }
    

    同样,此代码以与 Node/Windows 相同的方式解析命令字符串:

    C:\>node test2.js param1   "   param   2" param""3 "param "" 4  " "param 5
    commandLine
    'param1   "   param   2" param""3 "param "" 4  " "param 5'
    
    noDoubleDoubleQuotes
    'param1   "   param   2" param<DDQ>3 "param <DDQ> 4  " "param 5'
    
    noSpacesInQuotes
    'param1   <SP><SP><SP>param<SP><SP><SP>2 param<DDQ>3 param<SP>"<SP>4<SP><SP> param<SP>5'
    
    mangledParamArray
    [ 'param1',
      '<SP><SP><SP>param<SP><SP><SP>2',
      'param<DDQ>3',
      'param<SP>"<SP>4<SP><SP>',
      'param<SP>5' ]
    
    paramArray
    [ 'param1', '   param   2', 'param3', 'param " 4  ', 'param 5' ]
    
    argv
    [ 'param1', '   param   2', 'param3', 'param " 4  ', 'param 5' ]
    

    【讨论】:

      【解决方案3】:

      您可以找到引号的所有索引,并使用该信息将输入正确拆分,方法是将其传递给input.substring。像这样的东西应该可以工作:

      const input = '!file GrumpyCrouton \"Player had a bad time\" Kick \"1 Day\"';
      var raw = input;
      raw = raw.split(' ');
      let command = raw.splice(0, 1)[0]; // splice out the command (splice returns an array)
      let user = raw.splice(0, 1)[0];    // splice out the user
      
      let recompose = raw.join('');      // recompose the string WITHOUT any spaces
      
      let indices = []; // find the indices of the quotation marks
      for (var i in recompose) {
          let char = recompose[i];
        if (char === '"') {
          indices.push(i);
        }
      }
      
      console.log(indices, recompose);
      if (indices.length == 4) { // OK!
        // use the indices to break up input string into substrings
        let reason = recompose.substring(indices[0] + 1, indices[1]);
        let punishment = recompose.substring(indices[1], indices[2]).replace('"', '');
        let duration = recompose.substring(indices[2], indices[3]).replace('"', '');
        console.log(command);
        console.log(user);
        console.log(reason);
        console.log(punishment);
        console.log(duration);
      } else {
          // bad input!
      }
      

      您可以在jsfiddle 上尝试此代码!

      【讨论】:

      • 你也可以试试马特的建议,除了完全删除引号,像这样:!file GrumpyCrouton | Player had a bad time | Kick | 1 Day
      • 通过重新组合没有空格,这将得到Playerhadabadtime1Day。这可能是 OP 想要的,但这不是人们通常所期望的。它还依赖用双引号括起来的第二个和第四个字符串。
      【解决方案4】:

      您可以添加更明确的分隔符,例如“|”并使用 split('|')

      您的输入将类似于:!file GrumpyCrouton | “玩家度过了一段糟糕的时光” |踢 | “1天”

      【讨论】:

      • 我想要一种对用户期望影响较小的方式。我曾经使用过的每个命令系统都能够以这种方式使用,用空格分隔参数。好建议,但我想要一个更好的方法:)
      猜你喜欢
      • 2012-09-16
      • 1970-01-01
      • 2017-11-21
      • 1970-01-01
      • 2019-11-20
      • 1970-01-01
      • 2021-02-24
      • 2014-01-21
      • 1970-01-01
      相关资源
      最近更新 更多