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 redisReply *reply; 418 sds out; 419 420 if (redisGetReply(context,(void**)&reply) != REDIS_OK) { 421 if (config.shutdown) 422 return REDIS_OK; 423 if (config.interactive) { 424 /* Filter cases where we should reconnect */ 425 if (context->err == REDIS_ERR_IO && errno == ECONNRESET) 426 return REDIS_ERR; 427 if (context->err == REDIS_ERR_EOF) 428 return REDIS_ERR; 429 } 430 cliPrintContextErrorAndExit(); 431 return REDIS_ERR; /* avoid compiler warning */ 432 } 433 434 if (output_raw_strings) { 435 out = cliFormatReplyRaw(reply); 436 } else { 437 if (config.raw_output) { 438 out = cliFormatReplyRaw(reply); 439 out = sdscat(out,"\n"); 440 } else { 441 out = cliFormatReplyTTY(reply,""); 442 } 443 } 444 fwrite(out,sdslen(out),1,stdout); 445 sdsfree(out); 446 freeReplyObject(reply); 447 return REDIS_OK; 448 } 449 450 static int cliSendCommand(int argc, char **argv, int repeat) { 451 char *command = argv[0]; 452 size_t *argvlen; 453 int j, output_raw; 454 455 if (context == NULL) { 456 printf("Not connected, please use: connect <host> <port>\n"); 457 return REDIS_OK; 458 } 459 460 output_raw = !strcasecmp(command,"info"); 461 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { 462 cliOutputHelp(--argc, ++argv); 463 return REDIS_OK; 464 } 465 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 466 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 467 if (!strcasecmp(command,"subscribe") || 468 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 469 470 /* Setup argument length */ 471 argvlen = malloc(argc*sizeof(size_t)); 472 for (j = 0; j < argc; j++) 473 argvlen[j] = sdslen(argv[j]); 474 475 while(repeat--) { 476 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 477 while (config.monitor_mode) { 478 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 479 fflush(stdout); 480 } 481 482 if (config.pubsub_mode) { 483 if (!config.raw_output) 484 printf("Reading messages... (press Ctrl-C to quit)\n"); 485 while (1) { 486 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 487 } 488 } 489 490 if (cliReadReply(output_raw) != REDIS_OK) 491 return REDIS_ERR; 492 } 493 return REDIS_OK; 494 } 495 496 /*------------------------------------------------------------------------------ 497 * User interface 498 *--------------------------------------------------------------------------- */ 499 500 static int parseOptions(int argc, char **argv) { 501 int i; 502 503 for (i = 1; i < argc; i++) { 504 int lastarg = i==argc-1; 505 506 if (!strcmp(argv[i],"-h") && !lastarg) { 507 sdsfree(config.hostip); 508 config.hostip = sdsnew(argv[i+1]); 509 i++; 510 } else if (!strcmp(argv[i],"-h") && lastarg) { 511 usage(); 512 } else if (!strcmp(argv[i],"--help")) { 513 usage(); 514 } else if (!strcmp(argv[i],"-x")) { 515 config.stdinarg = 1; 516 } else if (!strcmp(argv[i],"-p") && !lastarg) { 517 config.hostport = atoi(argv[i+1]); 518 i++; 519 } else if (!strcmp(argv[i],"-s") && !lastarg) { 520 config.hostsocket = argv[i+1]; 521 i++; 522 } else if (!strcmp(argv[i],"-r") && !lastarg) { 523 config.repeat = strtoll(argv[i+1],NULL,10); 524 i++; 525 } else if (!strcmp(argv[i],"-n") && !lastarg) { 526 config.dbnum = atoi(argv[i+1]); 527 i++; 528 } else if (!strcmp(argv[i],"-a") && !lastarg) { 529 config.auth = argv[i+1]; 530 i++; 531 } else if (!strcmp(argv[i],"--raw")) { 532 config.raw_output = 1; 533 } else if (!strcmp(argv[i],"-d") && !lastarg) { 534 sdsfree(config.mb_delim); 535 config.mb_delim = sdsnew(argv[i+1]); 536 i++; 537 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 538 sds version = cliVersion(); 539 printf("redis-cli %s\n", version); 540 sdsfree(version); 541 exit(0); 542 } else { 543 break; 544 } 545 } 546 return i; 547 } 548 549 static sds readArgFromStdin(void) { 550 char buf[1024]; 551 sds arg = sdsempty(); 552 553 while(1) { 554 int nread = read(fileno(stdin),buf,1024); 555 556 if (nread == 0) break; 557 else if (nread == -1) { 558 perror("Reading from standard input"); 559 exit(1); 560 } 561 arg = sdscatlen(arg,buf,nread); 562 } 563 return arg; 564 } 565 566 static void usage() { 567 sds version = cliVersion(); 568 fprintf(stderr, 569 "redis-cli %s\n" 570 "\n" 571 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 572 " -h <hostname> Server hostname (default: 127.0.0.1)\n" 573 " -p <port> Server port (default: 6379)\n" 574 " -s <socket> Server socket (overrides hostname and port)\n" 575 " -a <password> Password to use when connecting to the server\n" 576 " -r <repeat> Execute specified command N times\n" 577 " -n <db> Database number\n" 578 " -x Read last argument from STDIN\n" 579 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n" 580 " --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" 581 " --help Output this help and exit\n" 582 " --version Output version and exit\n" 583 "\n" 584 "Examples:\n" 585 " cat /etc/passwd | redis-cli -x set mypasswd\n" 586 " redis-cli get mypasswd\n" 587 " redis-cli -r 100 lpush mylist x\n" 588 "\n" 589 "When no command is given, redis-cli starts in interactive mode.\n" 590 "Type \"help\" in interactive mode for information on available commands.\n" 591 "\n", 592 version); 593 sdsfree(version); 594 exit(1); 595 } 596 597 /* Turn the plain C strings into Sds strings */ 598 static char **convertToSds(int count, char** args) { 599 int j; 600 char **sds = zmalloc(sizeof(char*)*count); 601 602 for(j = 0; j < count; j++) 603 sds[j] = sdsnew(args[j]); 604 605 return sds; 606 } 607 608 #define LINE_BUFLEN 4096 609 static void repl() { 610 int argc, j; 611 char *line; 612 sds *argv; 613 614 config.interactive = 1; 615 linenoiseSetCompletionCallback(completionCallback); 616 617 while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) { 618 if (line[0] != '\0') { 619 argv = sdssplitargs(line,&argc); 620 linenoiseHistoryAdd(line); 621 if (config.historyfile) linenoiseHistorySave(config.historyfile); 622 if (argv == NULL) { 623 printf("Invalid argument(s)\n"); 624 continue; 625 } else if (argc > 0) { 626 if (strcasecmp(argv[0],"quit") == 0 || 627 strcasecmp(argv[0],"exit") == 0) 628 { 629 exit(0); 630 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 631 sdsfree(config.hostip); 632 config.hostip = sdsnew(argv[1]); 633 config.hostport = atoi(argv[2]); 634 cliConnect(1); 635 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 636 linenoiseClearScreen(); 637 } else { 638 long long start_time = mstime(), elapsed; 639 640 if (cliSendCommand(argc,argv,1) != REDIS_OK) { 641 cliConnect(1); 642 643 /* If we still cannot send the command, 644 * print error and abort. */ 645 if (cliSendCommand(argc,argv,1) != REDIS_OK) 646 cliPrintContextErrorAndExit(); 647 } 648 elapsed = mstime()-start_time; 649 if (elapsed >= 500) { 650 printf("(%.2fs)\n",(double)elapsed/1000); 651 } 652 } 653 } 654 /* Free the argument vector */ 655 for (j = 0; j < argc; j++) 656 sdsfree(argv[j]); 657 zfree(argv); 658 } 659 /* linenoise() returns malloc-ed lines like readline() */ 660 free(line); 661 } 662 exit(0); 663 } 664 665 static int noninteractive(int argc, char **argv) { 666 int retval = 0; 667 if (config.stdinarg) { 668 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 669 argv[argc] = readArgFromStdin(); 670 retval = cliSendCommand(argc+1, argv, config.repeat); 671 } else { 672 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 673 retval = cliSendCommand(argc, argv, config.repeat); 674 } 675 return retval; 676 } 677 678 int main(int argc, char **argv) { 679 int firstarg; 680 681 config.hostip = sdsnew("127.0.0.1"); 682 config.hostport = 6379; 683 config.hostsocket = NULL; 684 config.repeat = 1; 685 config.dbnum = 0; 686 config.interactive = 0; 687 config.shutdown = 0; 688 config.monitor_mode = 0; 689 config.pubsub_mode = 0; 690 config.stdinarg = 0; 691 config.auth = NULL; 692 config.historyfile = NULL; 693 config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL); 694 config.mb_delim = sdsnew("\n"); 695 cliInitHelp(); 696 697 if (getenv("HOME") != NULL) { 698 config.historyfile = malloc(256); 699 snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME")); 700 linenoiseHistoryLoad(config.historyfile); 701 } 702 703 firstarg = parseOptions(argc,argv); 704 argc -= firstarg; 705 argv += firstarg; 706 707 /* Try to connect */ 708 if (cliConnect(0) != REDIS_OK) exit(1); 709 710 /* Start interactive mode when no command is provided */ 711 if (argc == 0) repl(); 712 /* Otherwise, we have some arguments to execute */ 713 return noninteractive(argc,convertToSds(argc,argv)); 714 } 715