xref: /redis-3.2.3/src/redis-cli.c (revision c9d0c362)
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     long interval;
59     int dbnum;
60     int interactive;
61     int shutdown;
62     int monitor_mode;
63     int pubsub_mode;
64     int stdinarg; /* get last arg from stdin. (-x option) */
65     char *auth;
66     int raw_output; /* output mode per command */
67     sds mb_delim;
68     char prompt[32];
69 } config;
70 
71 static void usage();
72 char *redisGitSHA1(void);
73 char *redisGitDirty(void);
74 
75 /*------------------------------------------------------------------------------
76  * Utility functions
77  *--------------------------------------------------------------------------- */
78 
79 static long long mstime(void) {
80     struct timeval tv;
81     long long mst;
82 
83     gettimeofday(&tv, NULL);
84     mst = ((long)tv.tv_sec)*1000;
85     mst += tv.tv_usec/1000;
86     return mst;
87 }
88 
89 static void cliRefreshPrompt(void) {
90     if (config.dbnum == 0)
91         snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d> ",
92             config.hostip, config.hostport);
93     else
94         snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d[%d]> ",
95             config.hostip, config.hostport, config.dbnum);
96 }
97 
98 /*------------------------------------------------------------------------------
99  * Help functions
100  *--------------------------------------------------------------------------- */
101 
102 #define CLI_HELP_COMMAND 1
103 #define CLI_HELP_GROUP 2
104 
105 typedef struct {
106     int type;
107     int argc;
108     sds *argv;
109     sds full;
110 
111     /* Only used for help on commands */
112     struct commandHelp *org;
113 } helpEntry;
114 
115 static helpEntry *helpEntries;
116 static int helpEntriesLen;
117 
118 static sds cliVersion() {
119     sds version;
120     version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
121 
122     /* Add git commit and working tree status when available */
123     if (strtoll(redisGitSHA1(),NULL,16)) {
124         version = sdscatprintf(version, " (git:%s", redisGitSHA1());
125         if (strtoll(redisGitDirty(),NULL,10))
126             version = sdscatprintf(version, "-dirty");
127         version = sdscat(version, ")");
128     }
129     return version;
130 }
131 
132 static void cliInitHelp() {
133     int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
134     int groupslen = sizeof(commandGroups)/sizeof(char*);
135     int i, len, pos = 0;
136     helpEntry tmp;
137 
138     helpEntriesLen = len = commandslen+groupslen;
139     helpEntries = malloc(sizeof(helpEntry)*len);
140 
141     for (i = 0; i < groupslen; i++) {
142         tmp.argc = 1;
143         tmp.argv = malloc(sizeof(sds));
144         tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
145         tmp.full = tmp.argv[0];
146         tmp.type = CLI_HELP_GROUP;
147         tmp.org = NULL;
148         helpEntries[pos++] = tmp;
149     }
150 
151     for (i = 0; i < commandslen; i++) {
152         tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
153         tmp.full = sdsnew(commandHelp[i].name);
154         tmp.type = CLI_HELP_COMMAND;
155         tmp.org = &commandHelp[i];
156         helpEntries[pos++] = tmp;
157     }
158 }
159 
160 /* Output command help to stdout. */
161 static void cliOutputCommandHelp(struct commandHelp *help, int group) {
162     printf("\r\n  \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
163     printf("  \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
164     printf("  \x1b[33msince:\x1b[0m %s\r\n", help->since);
165     if (group) {
166         printf("  \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
167     }
168 }
169 
170 /* Print generic help. */
171 static void cliOutputGenericHelp() {
172     sds version = cliVersion();
173     printf(
174         "redis-cli %s\r\n"
175         "Type: \"help @<group>\" to get a list of commands in <group>\r\n"
176         "      \"help <command>\" for help on <command>\r\n"
177         "      \"help <tab>\" to get a list of possible help topics\r\n"
178         "      \"quit\" to exit\r\n",
179         version
180     );
181     sdsfree(version);
182 }
183 
184 /* Output all command help, filtering by group or command name. */
185 static void cliOutputHelp(int argc, char **argv) {
186     int i, j, len;
187     int group = -1;
188     helpEntry *entry;
189     struct commandHelp *help;
190 
191     if (argc == 0) {
192         cliOutputGenericHelp();
193         return;
194     } else if (argc > 0 && argv[0][0] == '@') {
195         len = sizeof(commandGroups)/sizeof(char*);
196         for (i = 0; i < len; i++) {
197             if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
198                 group = i;
199                 break;
200             }
201         }
202     }
203 
204     assert(argc > 0);
205     for (i = 0; i < helpEntriesLen; i++) {
206         entry = &helpEntries[i];
207         if (entry->type != CLI_HELP_COMMAND) continue;
208 
209         help = entry->org;
210         if (group == -1) {
211             /* Compare all arguments */
212             if (argc == entry->argc) {
213                 for (j = 0; j < argc; j++) {
214                     if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
215                 }
216                 if (j == argc) {
217                     cliOutputCommandHelp(help,1);
218                 }
219             }
220         } else {
221             if (group == help->group) {
222                 cliOutputCommandHelp(help,0);
223             }
224         }
225     }
226     printf("\r\n");
227 }
228 
229 static void completionCallback(const char *buf, linenoiseCompletions *lc) {
230     size_t startpos = 0;
231     int mask;
232     int i;
233     size_t matchlen;
234     sds tmp;
235 
236     if (strncasecmp(buf,"help ",5) == 0) {
237         startpos = 5;
238         while (isspace(buf[startpos])) startpos++;
239         mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
240     } else {
241         mask = CLI_HELP_COMMAND;
242     }
243 
244     for (i = 0; i < helpEntriesLen; i++) {
245         if (!(helpEntries[i].type & mask)) continue;
246 
247         matchlen = strlen(buf+startpos);
248         if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
249             tmp = sdsnewlen(buf,startpos);
250             tmp = sdscat(tmp,helpEntries[i].full);
251             linenoiseAddCompletion(lc,tmp);
252             sdsfree(tmp);
253         }
254     }
255 }
256 
257 /*------------------------------------------------------------------------------
258  * Networking / parsing
259  *--------------------------------------------------------------------------- */
260 
261 /* Send AUTH command to the server */
262 static int cliAuth() {
263     redisReply *reply;
264     if (config.auth == NULL) return REDIS_OK;
265 
266     reply = redisCommand(context,"AUTH %s",config.auth);
267     if (reply != NULL) {
268         freeReplyObject(reply);
269         return REDIS_OK;
270     }
271     return REDIS_ERR;
272 }
273 
274 /* Send SELECT dbnum to the server */
275 static int cliSelect() {
276     redisReply *reply;
277     if (config.dbnum == 0) return REDIS_OK;
278 
279     reply = redisCommand(context,"SELECT %d",config.dbnum);
280     if (reply != NULL) {
281         freeReplyObject(reply);
282         return REDIS_OK;
283     }
284     return REDIS_ERR;
285 }
286 
287 /* Connect to the client. If force is not zero the connection is performed
288  * even if there is already a connected socket. */
289 static int cliConnect(int force) {
290     if (context == NULL || force) {
291         if (context != NULL)
292             redisFree(context);
293 
294         if (config.hostsocket == NULL) {
295             context = redisConnect(config.hostip,config.hostport);
296         } else {
297             context = redisConnectUnix(config.hostsocket);
298         }
299 
300         if (context->err) {
301             fprintf(stderr,"Could not connect to Redis at ");
302             if (config.hostsocket == NULL)
303                 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
304             else
305                 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
306             redisFree(context);
307             context = NULL;
308             return REDIS_ERR;
309         }
310 
311         /* Do AUTH and select the right DB. */
312         if (cliAuth() != REDIS_OK)
313             return REDIS_ERR;
314         if (cliSelect() != REDIS_OK)
315             return REDIS_ERR;
316     }
317     return REDIS_OK;
318 }
319 
320 static void cliPrintContextError() {
321     if (context == NULL) return;
322     fprintf(stderr,"Error: %s\n",context->errstr);
323 }
324 
325 static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
326     sds out = sdsempty();
327     switch (r->type) {
328     case REDIS_REPLY_ERROR:
329         out = sdscatprintf(out,"(error) %s\n", r->str);
330     break;
331     case REDIS_REPLY_STATUS:
332         out = sdscat(out,r->str);
333         out = sdscat(out,"\n");
334     break;
335     case REDIS_REPLY_INTEGER:
336         out = sdscatprintf(out,"(integer) %lld\n",r->integer);
337     break;
338     case REDIS_REPLY_STRING:
339         /* If you are producing output for the standard output we want
340         * a more interesting output with quoted characters and so forth */
341         out = sdscatrepr(out,r->str,r->len);
342         out = sdscat(out,"\n");
343     break;
344     case REDIS_REPLY_NIL:
345         out = sdscat(out,"(nil)\n");
346     break;
347     case REDIS_REPLY_ARRAY:
348         if (r->elements == 0) {
349             out = sdscat(out,"(empty list or set)\n");
350         } else {
351             unsigned int i, idxlen = 0;
352             char _prefixlen[16];
353             char _prefixfmt[16];
354             sds _prefix;
355             sds tmp;
356 
357             /* Calculate chars needed to represent the largest index */
358             i = r->elements;
359             do {
360                 idxlen++;
361                 i /= 10;
362             } while(i);
363 
364             /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
365             memset(_prefixlen,' ',idxlen+2);
366             _prefixlen[idxlen+2] = '\0';
367             _prefix = sdscat(sdsnew(prefix),_prefixlen);
368 
369             /* Setup prefix format for every entry */
370             snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);
371 
372             for (i = 0; i < r->elements; i++) {
373                 /* Don't use the prefix for the first element, as the parent
374                  * caller already prepended the index number. */
375                 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
376 
377                 /* Format the multi bulk entry */
378                 tmp = cliFormatReplyTTY(r->element[i],_prefix);
379                 out = sdscatlen(out,tmp,sdslen(tmp));
380                 sdsfree(tmp);
381             }
382             sdsfree(_prefix);
383         }
384     break;
385     default:
386         fprintf(stderr,"Unknown reply type: %d\n", r->type);
387         exit(1);
388     }
389     return out;
390 }
391 
392 static sds cliFormatReplyRaw(redisReply *r) {
393     sds out = sdsempty(), tmp;
394     size_t i;
395 
396     switch (r->type) {
397     case REDIS_REPLY_NIL:
398         /* Nothing... */
399         break;
400     case REDIS_REPLY_ERROR:
401         out = sdscatlen(out,r->str,r->len);
402         out = sdscatlen(out,"\n",1);
403         break;
404     case REDIS_REPLY_STATUS:
405     case REDIS_REPLY_STRING:
406         out = sdscatlen(out,r->str,r->len);
407         break;
408     case REDIS_REPLY_INTEGER:
409         out = sdscatprintf(out,"%lld",r->integer);
410         break;
411     case REDIS_REPLY_ARRAY:
412         for (i = 0; i < r->elements; i++) {
413             if (i > 0) out = sdscat(out,config.mb_delim);
414             tmp = cliFormatReplyRaw(r->element[i]);
415             out = sdscatlen(out,tmp,sdslen(tmp));
416             sdsfree(tmp);
417         }
418         break;
419     default:
420         fprintf(stderr,"Unknown reply type: %d\n", r->type);
421         exit(1);
422     }
423     return out;
424 }
425 
426 static int cliReadReply(int output_raw_strings) {
427     void *_reply;
428     redisReply *reply;
429     sds out;
430 
431     if (redisGetReply(context,&_reply) != REDIS_OK) {
432         if (config.shutdown)
433             return REDIS_OK;
434         if (config.interactive) {
435             /* Filter cases where we should reconnect */
436             if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
437                 return REDIS_ERR;
438             if (context->err == REDIS_ERR_EOF)
439                 return REDIS_ERR;
440         }
441         cliPrintContextError();
442         exit(1);
443         return REDIS_ERR; /* avoid compiler warning */
444     }
445 
446     reply = (redisReply*)_reply;
447     if (output_raw_strings) {
448         out = cliFormatReplyRaw(reply);
449     } else {
450         if (config.raw_output) {
451             out = cliFormatReplyRaw(reply);
452             out = sdscat(out,"\n");
453         } else {
454             out = cliFormatReplyTTY(reply,"");
455         }
456     }
457     fwrite(out,sdslen(out),1,stdout);
458     sdsfree(out);
459     freeReplyObject(reply);
460     return REDIS_OK;
461 }
462 
463 static int cliSendCommand(int argc, char **argv, int repeat) {
464     char *command = argv[0];
465     size_t *argvlen;
466     int j, output_raw;
467 
468     if (context == NULL) return REDIS_ERR;
469 
470     output_raw = 0;
471     if (!strcasecmp(command,"info") ||
472         (argc == 2 && !strcasecmp(command,"cluster") &&
473                       (!strcasecmp(argv[1],"nodes") ||
474                        !strcasecmp(argv[1],"info"))) ||
475         (argc == 2 && !strcasecmp(command,"client") &&
476                        !strcasecmp(argv[1],"list")))
477 
478     {
479         output_raw = 1;
480     }
481 
482     if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
483         cliOutputHelp(--argc, ++argv);
484         return REDIS_OK;
485     }
486     if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
487     if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
488     if (!strcasecmp(command,"subscribe") ||
489         !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
490 
491     /* Setup argument length */
492     argvlen = malloc(argc*sizeof(size_t));
493     for (j = 0; j < argc; j++)
494         argvlen[j] = sdslen(argv[j]);
495 
496     while(repeat--) {
497         redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
498         while (config.monitor_mode) {
499             if (cliReadReply(output_raw) != REDIS_OK) exit(1);
500             fflush(stdout);
501         }
502 
503         if (config.pubsub_mode) {
504             if (!config.raw_output)
505                 printf("Reading messages... (press Ctrl-C to quit)\n");
506             while (1) {
507                 if (cliReadReply(output_raw) != REDIS_OK) exit(1);
508             }
509         }
510 
511         if (cliReadReply(output_raw) != REDIS_OK) {
512             free(argvlen);
513             return REDIS_ERR;
514         } else {
515             /* Store database number when SELECT was successfully executed. */
516             if (!strcasecmp(command,"select") && argc == 2) {
517                 config.dbnum = atoi(argv[1]);
518                 cliRefreshPrompt();
519             }
520         }
521         if (config.interval) usleep(config.interval);
522         fflush(stdout); /* Make it grep friendly */
523     }
524 
525     free(argvlen);
526     return REDIS_OK;
527 }
528 
529 /*------------------------------------------------------------------------------
530  * User interface
531  *--------------------------------------------------------------------------- */
532 
533 static int parseOptions(int argc, char **argv) {
534     int i;
535 
536     for (i = 1; i < argc; i++) {
537         int lastarg = i==argc-1;
538 
539         if (!strcmp(argv[i],"-h") && !lastarg) {
540             sdsfree(config.hostip);
541             config.hostip = sdsnew(argv[i+1]);
542             i++;
543         } else if (!strcmp(argv[i],"-h") && lastarg) {
544             usage();
545         } else if (!strcmp(argv[i],"--help")) {
546             usage();
547         } else if (!strcmp(argv[i],"-x")) {
548             config.stdinarg = 1;
549         } else if (!strcmp(argv[i],"-p") && !lastarg) {
550             config.hostport = atoi(argv[i+1]);
551             i++;
552         } else if (!strcmp(argv[i],"-s") && !lastarg) {
553             config.hostsocket = argv[i+1];
554             i++;
555         } else if (!strcmp(argv[i],"-r") && !lastarg) {
556             config.repeat = strtoll(argv[i+1],NULL,10);
557             i++;
558         } else if (!strcmp(argv[i],"-i") && !lastarg) {
559             double seconds = atof(argv[i+1]);
560             config.interval = seconds*1000000;
561             i++;
562         } else if (!strcmp(argv[i],"-n") && !lastarg) {
563             config.dbnum = atoi(argv[i+1]);
564             i++;
565         } else if (!strcmp(argv[i],"-a") && !lastarg) {
566             config.auth = argv[i+1];
567             i++;
568         } else if (!strcmp(argv[i],"--raw")) {
569             config.raw_output = 1;
570         } else if (!strcmp(argv[i],"-d") && !lastarg) {
571             sdsfree(config.mb_delim);
572             config.mb_delim = sdsnew(argv[i+1]);
573             i++;
574         } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
575             sds version = cliVersion();
576             printf("redis-cli %s\n", version);
577             sdsfree(version);
578             exit(0);
579         } else {
580             break;
581         }
582     }
583     return i;
584 }
585 
586 static sds readArgFromStdin(void) {
587     char buf[1024];
588     sds arg = sdsempty();
589 
590     while(1) {
591         int nread = read(fileno(stdin),buf,1024);
592 
593         if (nread == 0) break;
594         else if (nread == -1) {
595             perror("Reading from standard input");
596             exit(1);
597         }
598         arg = sdscatlen(arg,buf,nread);
599     }
600     return arg;
601 }
602 
603 static void usage() {
604     sds version = cliVersion();
605     fprintf(stderr,
606 "redis-cli %s\n"
607 "\n"
608 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
609 "  -h <hostname>    Server hostname (default: 127.0.0.1)\n"
610 "  -p <port>        Server port (default: 6379)\n"
611 "  -s <socket>      Server socket (overrides hostname and port)\n"
612 "  -a <password>    Password to use when connecting to the server\n"
613 "  -r <repeat>      Execute specified command N times\n"
614 "  -i <interval>    When -r is used, waits <interval> seconds per command.\n"
615 "                   It is possible to specify sub-second times like -i 0.1.\n"
616 "  -n <db>          Database number\n"
617 "  -x               Read last argument from STDIN\n"
618 "  -d <delimiter>   Multi-bulk delimiter in for raw formatting (default: \\n)\n"
619 "  --raw            Use raw formatting for replies (default when STDOUT is not a tty)\n"
620 "  --help           Output this help and exit\n"
621 "  --version        Output version and exit\n"
622 "\n"
623 "Examples:\n"
624 "  cat /etc/passwd | redis-cli -x set mypasswd\n"
625 "  redis-cli get mypasswd\n"
626 "  redis-cli -r 100 lpush mylist x\n"
627 "  redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
628 "\n"
629 "When no command is given, redis-cli starts in interactive mode.\n"
630 "Type \"help\" in interactive mode for information on available commands.\n"
631 "\n",
632         version);
633     sdsfree(version);
634     exit(1);
635 }
636 
637 /* Turn the plain C strings into Sds strings */
638 static char **convertToSds(int count, char** args) {
639   int j;
640   char **sds = zmalloc(sizeof(char*)*count);
641 
642   for(j = 0; j < count; j++)
643     sds[j] = sdsnew(args[j]);
644 
645   return sds;
646 }
647 
648 #define LINE_BUFLEN 4096
649 static void repl() {
650     sds historyfile = NULL;
651     int history = 0;
652     char *line;
653     int argc;
654     sds *argv;
655 
656     config.interactive = 1;
657     linenoiseSetCompletionCallback(completionCallback);
658 
659     /* Only use history when stdin is a tty. */
660     if (isatty(fileno(stdin))) {
661         history = 1;
662 
663         if (getenv("HOME") != NULL) {
664             historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
665             linenoiseHistoryLoad(historyfile);
666         }
667     }
668 
669     cliRefreshPrompt();
670     while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
671         if (line[0] != '\0') {
672             argv = sdssplitargs(line,&argc);
673             if (history) linenoiseHistoryAdd(line);
674             if (historyfile) linenoiseHistorySave(historyfile);
675 
676             if (argv == NULL) {
677                 printf("Invalid argument(s)\n");
678                 continue;
679             } else if (argc > 0) {
680                 if (strcasecmp(argv[0],"quit") == 0 ||
681                     strcasecmp(argv[0],"exit") == 0)
682                 {
683                     exit(0);
684                 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
685                     sdsfree(config.hostip);
686                     config.hostip = sdsnew(argv[1]);
687                     config.hostport = atoi(argv[2]);
688                     cliConnect(1);
689                 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
690                     linenoiseClearScreen();
691                 } else {
692                     long long start_time = mstime(), elapsed;
693                     int repeat, skipargs = 0;
694 
695                     repeat = atoi(argv[0]);
696                     if (repeat) {
697                         skipargs = 1;
698                     } else {
699                         repeat = 1;
700                     }
701 
702                     if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
703                         != REDIS_OK)
704                     {
705                         cliConnect(1);
706 
707                         /* If we still cannot send the command print error.
708                          * We'll try to reconnect the next time. */
709                         if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
710                             != REDIS_OK)
711                             cliPrintContextError();
712                     }
713                     elapsed = mstime()-start_time;
714                     if (elapsed >= 500) {
715                         printf("(%.2fs)\n",(double)elapsed/1000);
716                     }
717                 }
718             }
719             /* Free the argument vector */
720             while(argc--) sdsfree(argv[argc]);
721             zfree(argv);
722         }
723         /* linenoise() returns malloc-ed lines like readline() */
724         free(line);
725     }
726     exit(0);
727 }
728 
729 static int noninteractive(int argc, char **argv) {
730     int retval = 0;
731     if (config.stdinarg) {
732         argv = zrealloc(argv, (argc+1)*sizeof(char*));
733         argv[argc] = readArgFromStdin();
734         retval = cliSendCommand(argc+1, argv, config.repeat);
735     } else {
736         /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
737         retval = cliSendCommand(argc, argv, config.repeat);
738     }
739     return retval;
740 }
741 
742 int main(int argc, char **argv) {
743     int firstarg;
744 
745     config.hostip = sdsnew("127.0.0.1");
746     config.hostport = 6379;
747     config.hostsocket = NULL;
748     config.repeat = 1;
749     config.interval = 0;
750     config.dbnum = 0;
751     config.interactive = 0;
752     config.shutdown = 0;
753     config.monitor_mode = 0;
754     config.pubsub_mode = 0;
755     config.stdinarg = 0;
756     config.auth = NULL;
757     config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL);
758     config.mb_delim = sdsnew("\n");
759     cliInitHelp();
760 
761     firstarg = parseOptions(argc,argv);
762     argc -= firstarg;
763     argv += firstarg;
764 
765     /* Start interactive mode when no command is provided */
766     if (argc == 0) {
767         /* Note that in repl mode we don't abort on connection error.
768          * A new attempt will be performed for every command send. */
769         cliConnect(0);
770         repl();
771     }
772 
773     /* Otherwise, we have some arguments to execute */
774     if (cliConnect(0) != REDIS_OK) exit(1);
775     return noninteractive(argc,convertToSds(argc,argv));
776 }
777