问题
I'm attempting to make a Discord bot that does some interactions with a server.
I've written some code that sort of works, but there is a big problem with it. Here is my code:
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);
});
}
The command is !file {playername} {reason} {punishment} {duration}
, but the problem is, sometimes a few of the variables may have multiple words. For example, {reason}
could be something like "Player had a bad time" but my code is unable to parse this correctly because of the way the arguments are split up.
Let's say this command is entered:
!file GrumpyCrouton "Player had a bad time" Kick "1 Day"
But the arguments would actually be spread out differently, because the 3rd argument has spaces in it but the regex splits all of the argument by spaces regardless of quotes. Basically Discord ignores the quotes and uses each word as it's own argument, thus making the {punishment}
and {duration}
have an argument index that is 6 and 7 instead of 2 and 3, because every word is counted as an argument.
This is the way my arguments are read:
const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
How can I make it so strings enclosed in quotes are read as a single argument instead of multiple?
回答1:
I got to this question because I has a similar requirement to the OP (parsing a string that may contain double-quoted parameters with embedded spaces). However, the accepted answer did not do what I needed (it strips spaces, and assumes too much about the number of parameters). I therefore had to devise my own solution which I offer here in case anyone else finds it useful.
There are actually two variants: the first does not allow double-quotes to appear in the generated parameter list; the second does allow this by using a double-double-quote (...""...
) within a double-quoted string. (I actually wrote this version first "because that's how Node under Windows does things" and then cut it down for the first variant.
In both examples, the log()
function, and calls to it from within splitCommandLine()
, are purely to show the inner workings and can be omitted.
Simple Double-Quoted Strings
- Parameters will normally be split on spaces.
- Double-quoted strings are treated as one parameter even if they contain spaces.
- Multiple spaces within double-quotes will be preserved.
- Multiple spaces outside double-quotes they will be treated as a single space.
- If a final, closing double-quote is missing, it will be assumed.
- It is not possible to get a double-quote character into a parameter.
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 ;
}
Running this, with the same command-line as embedded in the code shows that it produces the same output as the Node/Windows command-line parser:
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' ]
Double-Quoted Strings with Double-Double-Quotes
- Exactly the same as the first example, except that within a double-quoted string, a double-double-quote (
..."aaa ""bbb"" ccc"...
) will insert a double-quote in the parsed-parameter (aaa "bbb" ccc
). Outside of a double-quoted string, a double-double-quote will be ignored. This mimics how Node under Windows parses the command-line (not tested on Unix-variants).
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 ;
}
Again, this code parses a command-string in the same way as 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' ]
回答2:
A simple regex will do the trick :)
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);
*/
回答3:
You could find all the indices of the quotation marks and use that information to split the input correctly by passing it to input.substring. Something like this should work:
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!
}
You can give this code a try on jsfiddle!
回答4:
You could add a more explicit delimiter like "|" and use split('|')
Your input would be like: !file GrumpyCrouton | "Player had a bad time" | Kick | "1 Day"
来源:https://stackoverflow.com/questions/50671649/capture-strings-in-quotes-as-single-command-argument