1 /* Redis CLI (command line interface) 2 * 3 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * * Neither the name of Redis nor the names of its contributors may be used 15 * to endorse or promote products derived from this software without 16 * specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "fmacros.h" 32 #include "version.h" 33 34 #include <stdio.h> 35 #include <string.h> 36 #include <stdlib.h> 37 #include <unistd.h> 38 #include <ctype.h> 39 #include <errno.h> 40 #include <sys/stat.h> 41 #include <sys/time.h> 42 #include <assert.h> 43 44 #include "hiredis.h" 45 #include "sds.h" 46 #include "zmalloc.h" 47 #include "linenoise.h" 48 #include "help.h" 49 50 #define REDIS_NOTUSED(V) ((void) V) 51 52 static redisContext *context; 53 static struct config { 54 char *hostip; 55 int hostport; 56 char *hostsocket; 57 long repeat; 58 int dbnum; 59 int interactive; 60 int shutdown; 61 int monitor_mode; 62 int pubsub_mode; 63 int stdinarg; /* get last arg from stdin. (-x option) */ 64 char *auth; 65 char *historyfile; 66 int raw_output; /* output mode per command */ 67 sds mb_delim; 68 } config; 69 70 static void usage(); 71 char *redisGitSHA1(void); 72 char *redisGitDirty(void); 73 74 /*------------------------------------------------------------------------------ 75 * Utility functions 76 *--------------------------------------------------------------------------- */ 77 78 static long long mstime(void) { 79 struct timeval tv; 80 long long mst; 81 82 gettimeofday(&tv, NULL); 83 mst = ((long)tv.tv_sec)*1000; 84 mst += tv.tv_usec/1000; 85 return mst; 86 } 87 88 /*------------------------------------------------------------------------------ 89 * Help functions 90 *--------------------------------------------------------------------------- */ 91 92 #define CLI_HELP_COMMAND 1 93 #define CLI_HELP_GROUP 2 94 95 typedef struct { 96 int type; 97 int argc; 98 sds *argv; 99 sds full; 100 101 /* Only used for help on commands */ 102 struct commandHelp *org; 103 } helpEntry; 104 105 static helpEntry *helpEntries; 106 static int helpEntriesLen; 107 108 static sds cliVersion() { 109 sds version; 110 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); 111 112 /* Add git commit and working tree status when available */ 113 if (strtoll(redisGitSHA1(),NULL,16)) { 114 version = sdscatprintf(version, " (git:%s", redisGitSHA1()); 115 if (strtoll(redisGitDirty(),NULL,10)) 116 version = sdscatprintf(version, "-dirty"); 117 version = sdscat(version, ")"); 118 } 119 return version; 120 } 121 122 static void cliInitHelp() { 123 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); 124 int groupslen = sizeof(commandGroups)/sizeof(char*); 125 int i, len, pos = 0; 126 helpEntry tmp; 127 128 helpEntriesLen = len = commandslen+groupslen; 129 helpEntries = malloc(sizeof(helpEntry)*len); 130 131 for (i = 0; i < groupslen; i++) { 132 tmp.argc = 1; 133 tmp.argv = malloc(sizeof(sds)); 134 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); 135 tmp.full = tmp.argv[0]; 136 tmp.type = CLI_HELP_GROUP; 137 tmp.org = NULL; 138 helpEntries[pos++] = tmp; 139 } 140 141 for (i = 0; i < commandslen; i++) { 142 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); 143 tmp.full = sdsnew(commandHelp[i].name); 144 tmp.type = CLI_HELP_COMMAND; 145 tmp.org = &commandHelp[i]; 146 helpEntries[pos++] = tmp; 147 } 148 } 149 150 /* Output command help to stdout. */ 151 static void cliOutputCommandHelp(struct commandHelp *help, int group) { 152 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); 153 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); 154 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); 155 if (group) { 156 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); 157 } 158 } 159 160 /* Print generic help. */ 161 static void cliOutputGenericHelp() { 162 sds version = cliVersion(); 163 printf( 164 "redis-cli %s\r\n" 165 "Type: \"help @<group>\" to get a list of commands in <group>\r\n" 166 " \"help <command>\" for help on <command>\r\n" 167 " \"help <tab>\" to get a list of possible help topics\r\n" 168 " \"quit\" to exit\r\n", 169 version 170 ); 171 sdsfree(version); 172 } 173 174 /* Output all command help, filtering by group or command name. */ 175 static void cliOutputHelp(int argc, char **argv) { 176 int i, j, len; 177 int group = -1; 178 helpEntry *entry; 179 struct commandHelp *help; 180 181 if (argc == 0) { 182 cliOutputGenericHelp(); 183 return; 184 } else if (argc > 0 && argv[0][0] == '@') { 185 len = sizeof(commandGroups)/sizeof(char*); 186 for (i = 0; i < len; i++) { 187 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { 188 group = i; 189 break; 190 } 191 } 192 } 193 194 assert(argc > 0); 195 for (i = 0; i < helpEntriesLen; i++) { 196 entry = &helpEntries[i]; 197 if (entry->type != CLI_HELP_COMMAND) continue; 198 199 help = entry->org; 200 if (group == -1) { 201 /* Compare all arguments */ 202 if (argc == entry->argc) { 203 for (j = 0; j < argc; j++) { 204 if (strcasecmp(argv[j],entry->argv[j]) != 0) break; 205 } 206 if (j == argc) { 207 cliOutputCommandHelp(help,1); 208 } 209 } 210 } else { 211 if (group == help->group) { 212 cliOutputCommandHelp(help,0); 213 } 214 } 215 } 216 printf("\r\n"); 217 } 218 219 static void completionCallback(const char *buf, linenoiseCompletions *lc) { 220 size_t startpos = 0; 221 int mask; 222 int i; 223 size_t matchlen; 224 sds tmp; 225 226 if (strncasecmp(buf,"help ",5) == 0) { 227 startpos = 5; 228 while (isspace(buf[startpos])) startpos++; 229 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; 230 } else { 231 mask = CLI_HELP_COMMAND; 232 } 233 234 for (i = 0; i < helpEntriesLen; i++) { 235 if (!(helpEntries[i].type & mask)) continue; 236 237 matchlen = strlen(buf+startpos); 238 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { 239 tmp = sdsnewlen(buf,startpos); 240 tmp = sdscat(tmp,helpEntries[i].full); 241 linenoiseAddCompletion(lc,tmp); 242 sdsfree(tmp); 243 } 244 } 245 } 246 247 /*------------------------------------------------------------------------------ 248 * Networking / parsing 249 *--------------------------------------------------------------------------- */ 250 251 /* Send AUTH command to the server */ 252 static int cliAuth() { 253 redisReply *reply; 254 if (config.auth == NULL) return REDIS_OK; 255 256 reply = redisCommand(context,"AUTH %s",config.auth); 257 if (reply != NULL) { 258 freeReplyObject(reply); 259 return REDIS_OK; 260 } 261 return REDIS_ERR; 262 } 263 264 /* Send SELECT dbnum to the server */ 265 static int cliSelect() { 266 redisReply *reply; 267 char dbnum[16]; 268 if (config.dbnum == 0) return REDIS_OK; 269 270 snprintf(dbnum,sizeof(dbnum),"%d",config.dbnum); 271 reply = redisCommand(context,"SELECT %s",dbnum); 272 if (reply != NULL) { 273 freeReplyObject(reply); 274 return REDIS_OK; 275 } 276 return REDIS_ERR; 277 } 278 279 /* Connect to the client. If force is not zero the connection is performed 280 * even if there is already a connected socket. */ 281 static int cliConnect(int force) { 282 if (context == NULL || force) { 283 if (context != NULL) 284 redisFree(context); 285 286 if (config.hostsocket == NULL) { 287 context = redisConnect(config.hostip,config.hostport); 288 } else { 289 context = redisConnectUnix(config.hostsocket); 290 } 291 292 if (context->err) { 293 fprintf(stderr,"Could not connect to Redis at "); 294 if (config.hostsocket == NULL) 295 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); 296 else 297 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); 298 redisFree(context); 299 context = NULL; 300 return REDIS_ERR; 301 } 302 303 /* Do AUTH and select the right DB. */ 304 if (cliAuth() != REDIS_OK) 305 return REDIS_ERR; 306 if (cliSelect() != REDIS_OK) 307 return REDIS_ERR; 308 } 309 return REDIS_OK; 310 } 311 312 static void cliPrintContextErrorAndExit() { 313 if (context == NULL) return; 314 fprintf(stderr,"Error: %s\n",context->errstr); 315 exit(1); 316 } 317 318 static sds cliFormatReplyTTY(redisReply *r, char *prefix) { 319 sds out = sdsempty(); 320 switch (r->type) { 321 case REDIS_REPLY_ERROR: 322 out = sdscatprintf(out,"(error) %s\n", r->str); 323 break; 324 case REDIS_REPLY_STATUS: 325 out = sdscat(out,r->str); 326 out = sdscat(out,"\n"); 327 break; 328 case REDIS_REPLY_INTEGER: 329 out = sdscatprintf(out,"(integer) %lld\n",r->integer); 330 break; 331 case REDIS_REPLY_STRING: 332 /* If you are producing output for the standard output we want 333 * a more interesting output with quoted characters and so forth */ 334 out = sdscatrepr(out,r->str,r->len); 335 out = sdscat(out,"\n"); 336 break; 337 case REDIS_REPLY_NIL: 338 out = sdscat(out,"(nil)\n"); 339 break; 340 case REDIS_REPLY_ARRAY: 341 if (r->elements == 0) { 342 out = sdscat(out,"(empty list or set)\n"); 343 } else { 344 unsigned int i, idxlen = 0; 345 char _prefixlen[16]; 346 char _prefixfmt[16]; 347 sds _prefix; 348 sds tmp; 349 350 /* Calculate chars needed to represent the largest index */ 351 i = r->elements; 352 do { 353 idxlen++; 354 i /= 10; 355 } while(i); 356 357 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ 358 memset(_prefixlen,' ',idxlen+2); 359 _prefixlen[idxlen+2] = '\0'; 360 _prefix = sdscat(sdsnew(prefix),_prefixlen); 361 362 /* Setup prefix format for every entry */ 363 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); 364 365 for (i = 0; i < r->elements; i++) { 366 /* Don't use the prefix for the first element, as the parent 367 * caller already prepended the index number. */ 368 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); 369 370 /* Format the multi bulk entry */ 371 tmp = cliFormatReplyTTY(r->element[i],_prefix); 372 out = sdscatlen(out,tmp,sdslen(tmp)); 373 sdsfree(tmp); 374 } 375 sdsfree(_prefix); 376 } 377 break; 378 default: 379 fprintf(stderr,"Unknown reply type: %d\n", r->type); 380 exit(1); 381 } 382 return out; 383 } 384 385 static sds cliFormatReplyRaw(redisReply *r) { 386 sds out = sdsempty(), tmp; 387 size_t i; 388 389 switch (r->type) { 390 case REDIS_REPLY_NIL: 391 /* Nothing... */ 392 break; 393 case REDIS_REPLY_ERROR: 394 case REDIS_REPLY_STATUS: 395 case REDIS_REPLY_STRING: 396 out = sdscatlen(out,r->str,r->len); 397 break; 398 case REDIS_REPLY_INTEGER: 399 out = sdscatprintf(out,"%lld",r->integer); 400 break; 401 case REDIS_REPLY_ARRAY: 402 for (i = 0; i < r->elements; i++) { 403 if (i > 0) out = sdscat(out,config.mb_delim); 404 tmp = cliFormatReplyRaw(r->element[i]); 405 out = sdscatlen(out,tmp,sdslen(tmp)); 406 sdsfree(tmp); 407 } 408 break; 409 default: 410 fprintf(stderr,"Unknown reply type: %d\n", r->type); 411 exit(1); 412 } 413 return out; 414 } 415 416 static int cliReadReply(int output_raw_strings) { 417 void *_reply; 418 redisReply *reply; 419 sds out; 420 421 if (redisGetReply(context,&_reply) != REDIS_OK) { 422 if (config.shutdown) 423 return REDIS_OK; 424 if (config.interactive) { 425 /* Filter cases where we should reconnect */ 426 if (context->err == REDIS_ERR_IO && errno == ECONNRESET) 427 return REDIS_ERR; 428 if (context->err == REDIS_ERR_EOF) 429 return REDIS_ERR; 430 } 431 cliPrintContextErrorAndExit(); 432 return REDIS_ERR; /* avoid compiler warning */ 433 } 434 435 reply = (redisReply*)_reply; 436 if (output_raw_strings) { 437 out = cliFormatReplyRaw(reply); 438 } else { 439 if (config.raw_output) { 440 out = cliFormatReplyRaw(reply); 441 out = sdscat(out,"\n"); 442 } else { 443 out = cliFormatReplyTTY(reply,""); 444 } 445 } 446 fwrite(out,sdslen(out),1,stdout); 447 sdsfree(out); 448 freeReplyObject(reply); 449 return REDIS_OK; 450 } 451 452 static int cliSendCommand(int argc, char **argv, int repeat) { 453 char *command = argv[0]; 454 size_t *argvlen; 455 int j, output_raw; 456 457 if (context == NULL) { 458 printf("Not connected, please use: connect <host> <port>\n"); 459 return REDIS_OK; 460 } 461 462 output_raw = !strcasecmp(command,"info"); 463 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { 464 cliOutputHelp(--argc, ++argv); 465 return REDIS_OK; 466 } 467 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 468 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 469 if (!strcasecmp(command,"subscribe") || 470 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 471 472 /* Setup argument length */ 473 argvlen = malloc(argc*sizeof(size_t)); 474 for (j = 0; j < argc; j++) 475 argvlen[j] = sdslen(argv[j]); 476 477 while(repeat--) { 478 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 479 while (config.monitor_mode) { 480 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 481 fflush(stdout); 482 } 483 484 if (config.pubsub_mode) { 485 if (!config.raw_output) 486 printf("Reading messages... (press Ctrl-C to quit)\n"); 487 while (1) { 488 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 489 } 490 } 491 492 if (cliReadReply(output_raw) != REDIS_OK) 493 return REDIS_ERR; 494 } 495 return REDIS_OK; 496 } 497 498 /*------------------------------------------------------------------------------ 499 * User interface 500 *--------------------------------------------------------------------------- */ 501 502 static int parseOptions(int argc, char **argv) { 503 int i; 504 505 for (i = 1; i < argc; i++) { 506 int lastarg = i==argc-1; 507 508 if (!strcmp(argv[i],"-h") && !lastarg) { 509 sdsfree(config.hostip); 510 config.hostip = sdsnew(argv[i+1]); 511 i++; 512 } else if (!strcmp(argv[i],"-h") && lastarg) { 513 usage(); 514 } else if (!strcmp(argv[i],"--help")) { 515 usage(); 516 } else if (!strcmp(argv[i],"-x")) { 517 config.stdinarg = 1; 518 } else if (!strcmp(argv[i],"-p") && !lastarg) { 519 config.hostport = atoi(argv[i+1]); 520 i++; 521 } else if (!strcmp(argv[i],"-s") && !lastarg) { 522 config.hostsocket = argv[i+1]; 523 i++; 524 } else if (!strcmp(argv[i],"-r") && !lastarg) { 525 config.repeat = strtoll(argv[i+1],NULL,10); 526 i++; 527 } else if (!strcmp(argv[i],"-n") && !lastarg) { 528 config.dbnum = atoi(argv[i+1]); 529 i++; 530 } else if (!strcmp(argv[i],"-a") && !lastarg) { 531 config.auth = argv[i+1]; 532 i++; 533 } else if (!strcmp(argv[i],"--raw")) { 534 config.raw_output = 1; 535 } else if (!strcmp(argv[i],"-d") && !lastarg) { 536 sdsfree(config.mb_delim); 537 config.mb_delim = sdsnew(argv[i+1]); 538 i++; 539 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 540 sds version = cliVersion(); 541 printf("redis-cli %s\n", version); 542 sdsfree(version); 543 exit(0); 544 } else { 545 break; 546 } 547 } 548 return i; 549 } 550 551 static sds readArgFromStdin(void) { 552 char buf[1024]; 553 sds arg = sdsempty(); 554 555 while(1) { 556 int nread = read(fileno(stdin),buf,1024); 557 558 if (nread == 0) break; 559 else if (nread == -1) { 560 perror("Reading from standard input"); 561 exit(1); 562 } 563 arg = sdscatlen(arg,buf,nread); 564 } 565 return arg; 566 } 567 568 static void usage() { 569 sds version = cliVersion(); 570 fprintf(stderr, 571 "redis-cli %s\n" 572 "\n" 573 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 574 " -h <hostname> Server hostname (default: 127.0.0.1)\n" 575 " -p <port> Server port (default: 6379)\n" 576 " -s <socket> Server socket (overrides hostname and port)\n" 577 " -a <password> Password to use when connecting to the server\n" 578 " -r <repeat> Execute specified command N times\n" 579 " -n <db> Database number\n" 580 " -x Read last argument from STDIN\n" 581 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n" 582 " --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" 583 " --help Output this help and exit\n" 584 " --version Output version and exit\n" 585 "\n" 586 "Examples:\n" 587 " cat /etc/passwd | redis-cli -x set mypasswd\n" 588 " redis-cli get mypasswd\n" 589 " redis-cli -r 100 lpush mylist x\n" 590 "\n" 591 "When no command is given, redis-cli starts in interactive mode.\n" 592 "Type \"help\" in interactive mode for information on available commands.\n" 593 "\n", 594 version); 595 sdsfree(version); 596 exit(1); 597 } 598 599 /* Turn the plain C strings into Sds strings */ 600 static char **convertToSds(int count, char** args) { 601 int j; 602 char **sds = zmalloc(sizeof(char*)*count); 603 604 for(j = 0; j < count; j++) 605 sds[j] = sdsnew(args[j]); 606 607 return sds; 608 } 609 610 #define LINE_BUFLEN 4096 611 static void repl() { 612 int argc, j; 613 char *line; 614 sds *argv; 615 616 config.interactive = 1; 617 linenoiseSetCompletionCallback(completionCallback); 618 619 while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) { 620 if (line[0] != '\0') { 621 argv = sdssplitargs(line,&argc); 622 linenoiseHistoryAdd(line); 623 if (config.historyfile) linenoiseHistorySave(config.historyfile); 624 if (argv == NULL) { 625 printf("Invalid argument(s)\n"); 626 continue; 627 } else if (argc > 0) { 628 if (strcasecmp(argv[0],"quit") == 0 || 629 strcasecmp(argv[0],"exit") == 0) 630 { 631 exit(0); 632 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 633 sdsfree(config.hostip); 634 config.hostip = sdsnew(argv[1]); 635 config.hostport = atoi(argv[2]); 636 cliConnect(1); 637 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 638 linenoiseClearScreen(); 639 } else { 640 long long start_time = mstime(), elapsed; 641 642 if (cliSendCommand(argc,argv,1) != REDIS_OK) { 643 cliConnect(1); 644 645 /* If we still cannot send the command, 646 * print error and abort. */ 647 if (cliSendCommand(argc,argv,1) != REDIS_OK) 648 cliPrintContextErrorAndExit(); 649 } 650 elapsed = mstime()-start_time; 651 if (elapsed >= 500) { 652 printf("(%.2fs)\n",(double)elapsed/1000); 653 } 654 } 655 } 656 /* Free the argument vector */ 657 for (j = 0; j < argc; j++) 658 sdsfree(argv[j]); 659 zfree(argv); 660 } 661 /* linenoise() returns malloc-ed lines like readline() */ 662 free(line); 663 } 664 exit(0); 665 } 666 667 static int noninteractive(int argc, char **argv) { 668 int retval = 0; 669 if (config.stdinarg) { 670 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 671 argv[argc] = readArgFromStdin(); 672 retval = cliSendCommand(argc+1, argv, config.repeat); 673 } else { 674 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 675 retval = cliSendCommand(argc, argv, config.repeat); 676 } 677 return retval; 678 } 679 680 int main(int argc, char **argv) { 681 int firstarg; 682 683 config.hostip = sdsnew("127.0.0.1"); 684 config.hostport = 6379; 685 config.hostsocket = NULL; 686 config.repeat = 1; 687 config.dbnum = 0; 688 config.interactive = 0; 689 config.shutdown = 0; 690 config.monitor_mode = 0; 691 config.pubsub_mode = 0; 692 config.stdinarg = 0; 693 config.auth = NULL; 694 config.historyfile = NULL; 695 config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL); 696 config.mb_delim = sdsnew("\n"); 697 cliInitHelp(); 698 699 if (getenv("HOME") != NULL) { 700 config.historyfile = malloc(256); 701 snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME")); 702 linenoiseHistoryLoad(config.historyfile); 703 } 704 705 firstarg = parseOptions(argc,argv); 706 argc -= firstarg; 707 argv += firstarg; 708 709 /* Try to connect */ 710 if (cliConnect(0) != REDIS_OK) exit(1); 711 712 /* Start interactive mode when no command is provided */ 713 if (argc == 0) repl(); 714 /* Otherwise, we have some arguments to execute */ 715 return noninteractive(argc,convertToSds(argc,argv)); 716 } 717