]> git.saurik.com Git - redis.git/blob - src/redis-cli.c
4d698158391b1858c9f0539822169714cfff6d68
[redis.git] / src / redis-cli.c
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 raw_output; /* output mode per command */
64 int tty; /* flag for default output format */
65 int stdinarg; /* get last arg from stdin. (-x option) */
66 char mb_sep;
67 char *auth;
68 char *historyfile;
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 /*------------------------------------------------------------------------------
90 * Help functions
91 *--------------------------------------------------------------------------- */
92
93 #define CLI_HELP_COMMAND 1
94 #define CLI_HELP_GROUP 2
95
96 typedef struct {
97 int type;
98 int argc;
99 sds *argv;
100 sds full;
101
102 /* Only used for help on commands */
103 struct commandHelp *org;
104 } helpEntry;
105
106 static helpEntry *helpEntries;
107 static int helpEntriesLen;
108
109 static sds cliVersion() {
110 sds version;
111 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
112
113 /* Add git commit and working tree status when available */
114 if (strtoll(redisGitSHA1(),NULL,16)) {
115 version = sdscatprintf(version, " (git:%s", redisGitSHA1());
116 if (strtoll(redisGitDirty(),NULL,10))
117 version = sdscatprintf(version, "-dirty");
118 version = sdscat(version, ")");
119 }
120 return version;
121 }
122
123 static void cliInitHelp() {
124 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
125 int groupslen = sizeof(commandGroups)/sizeof(char*);
126 int i, len, pos = 0;
127 helpEntry tmp;
128
129 helpEntriesLen = len = commandslen+groupslen;
130 helpEntries = malloc(sizeof(helpEntry)*len);
131
132 for (i = 0; i < groupslen; i++) {
133 tmp.argc = 1;
134 tmp.argv = malloc(sizeof(sds));
135 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
136 tmp.full = tmp.argv[0];
137 tmp.type = CLI_HELP_GROUP;
138 tmp.org = NULL;
139 helpEntries[pos++] = tmp;
140 }
141
142 for (i = 0; i < commandslen; i++) {
143 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
144 tmp.full = sdsnew(commandHelp[i].name);
145 tmp.type = CLI_HELP_COMMAND;
146 tmp.org = &commandHelp[i];
147 helpEntries[pos++] = tmp;
148 }
149 }
150
151 /* Output command help to stdout. */
152 static void cliOutputCommandHelp(struct commandHelp *help, int group) {
153 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
154 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
155 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since);
156 if (group) {
157 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
158 }
159 }
160
161 /* Print generic help. */
162 static void cliOutputGenericHelp() {
163 sds version = cliVersion();
164 printf(
165 "redis-cli %s\r\n"
166 "Type: \"help @<group>\" to get a list of commands in <group>\r\n"
167 " \"help <command>\" for help on <command>\r\n"
168 " \"help <tab>\" to get a list of possible help topics\r\n"
169 " \"quit\" to exit\r\n",
170 version
171 );
172 sdsfree(version);
173 }
174
175 /* Output all command help, filtering by group or command name. */
176 static void cliOutputHelp(int argc, char **argv) {
177 int i, j, len;
178 int group = -1;
179 helpEntry *entry;
180 struct commandHelp *help;
181
182 if (argc == 0) {
183 cliOutputGenericHelp();
184 return;
185 } else if (argc > 0 && argv[0][0] == '@') {
186 len = sizeof(commandGroups)/sizeof(char*);
187 for (i = 0; i < len; i++) {
188 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
189 group = i;
190 break;
191 }
192 }
193 }
194
195 assert(argc > 0);
196 for (i = 0; i < helpEntriesLen; i++) {
197 entry = &helpEntries[i];
198 if (entry->type != CLI_HELP_COMMAND) continue;
199
200 help = entry->org;
201 if (group == -1) {
202 /* Compare all arguments */
203 if (argc == entry->argc) {
204 for (j = 0; j < argc; j++) {
205 if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
206 }
207 if (j == argc) {
208 cliOutputCommandHelp(help,1);
209 }
210 }
211 } else {
212 if (group == help->group) {
213 cliOutputCommandHelp(help,0);
214 }
215 }
216 }
217 printf("\r\n");
218 }
219
220 static void completionCallback(const char *buf, linenoiseCompletions *lc) {
221 size_t startpos = 0;
222 int mask;
223 int i;
224 size_t matchlen;
225 sds tmp;
226
227 if (strncasecmp(buf,"help ",5) == 0) {
228 startpos = 5;
229 while (isspace(buf[startpos])) startpos++;
230 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
231 } else {
232 mask = CLI_HELP_COMMAND;
233 }
234
235 for (i = 0; i < helpEntriesLen; i++) {
236 if (!(helpEntries[i].type & mask)) continue;
237
238 matchlen = strlen(buf+startpos);
239 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
240 tmp = sdsnewlen(buf,startpos);
241 tmp = sdscat(tmp,helpEntries[i].full);
242 linenoiseAddCompletion(lc,tmp);
243 sdsfree(tmp);
244 }
245 }
246 }
247
248 /*------------------------------------------------------------------------------
249 * Networking / parsing
250 *--------------------------------------------------------------------------- */
251
252 /* Send AUTH command to the server */
253 static int cliAuth() {
254 redisReply *reply;
255 if (config.auth == NULL) return REDIS_OK;
256
257 reply = redisCommand(context,"AUTH %s",config.auth);
258 if (reply != NULL) {
259 freeReplyObject(reply);
260 return REDIS_OK;
261 }
262 return REDIS_ERR;
263 }
264
265 /* Send SELECT dbnum to the server */
266 static int cliSelect() {
267 redisReply *reply;
268 char dbnum[16];
269 if (config.dbnum == 0) return REDIS_OK;
270
271 snprintf(dbnum,sizeof(dbnum),"%d",config.dbnum);
272 reply = redisCommand(context,"SELECT %s",dbnum);
273 if (reply != NULL) {
274 freeReplyObject(reply);
275 return REDIS_OK;
276 }
277 return REDIS_ERR;
278 }
279
280 /* Connect to the client. If force is not zero the connection is performed
281 * even if there is already a connected socket. */
282 static int cliConnect(int force) {
283 if (context == NULL || force) {
284 if (context != NULL)
285 redisFree(context);
286
287 if (config.hostsocket == NULL) {
288 context = redisConnect(config.hostip,config.hostport);
289 } else {
290 context = redisConnectUnix(config.hostsocket);
291 }
292
293 if (context->err) {
294 fprintf(stderr,"Could not connect to Redis at ");
295 if (config.hostsocket == NULL)
296 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
297 else
298 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
299 redisFree(context);
300 context = NULL;
301 return REDIS_ERR;
302 }
303
304 /* Do AUTH and select the right DB. */
305 if (cliAuth() != REDIS_OK)
306 return REDIS_ERR;
307 if (cliSelect() != REDIS_OK)
308 return REDIS_ERR;
309 }
310 return REDIS_OK;
311 }
312
313 static void cliPrintContextErrorAndExit() {
314 if (context == NULL) return;
315 fprintf(stderr,"Error: %s\n",context->errstr);
316 exit(1);
317 }
318
319 static sds cliFormatReply(redisReply *r, char *prefix) {
320 sds out = sdsempty();
321 switch (r->type) {
322 case REDIS_REPLY_ERROR:
323 if (config.tty) out = sdscat(out,"(error) ");
324 out = sdscatprintf(out,"%s\n", r->str);
325 break;
326 case REDIS_REPLY_STATUS:
327 out = sdscat(out,r->str);
328 out = sdscat(out,"\n");
329 break;
330 case REDIS_REPLY_INTEGER:
331 if (config.tty) out = sdscat(out,"(integer) ");
332 out = sdscatprintf(out,"%lld\n",r->integer);
333 break;
334 case REDIS_REPLY_STRING:
335 if (config.raw_output || !config.tty) {
336 out = sdscatlen(out,r->str,r->len);
337 } else {
338 /* If you are producing output for the standard output we want
339 * a more interesting output with quoted characters and so forth */
340 out = sdscatrepr(out,r->str,r->len);
341 out = sdscat(out,"\n");
342 }
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 = cliFormatReply(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 int cliReadReply() {
393 redisReply *reply;
394 sds out;
395
396 if (redisGetReply(context,(void**)&reply) != REDIS_OK) {
397 if (config.shutdown)
398 return REDIS_OK;
399 if (config.interactive) {
400 /* Filter cases where we should reconnect */
401 if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
402 return REDIS_ERR;
403 if (context->err == REDIS_ERR_EOF)
404 return REDIS_ERR;
405 }
406 cliPrintContextErrorAndExit();
407 return REDIS_ERR; /* avoid compiler warning */
408 }
409
410 out = cliFormatReply(reply,"");
411 freeReplyObject(reply);
412 fwrite(out,sdslen(out),1,stdout);
413 sdsfree(out);
414 return REDIS_OK;
415 }
416
417 static int cliSendCommand(int argc, char **argv, int repeat) {
418 char *command = argv[0];
419 size_t *argvlen;
420 int j;
421
422 if (context == NULL) {
423 printf("Not connected, please use: connect <host> <port>\n");
424 return REDIS_OK;
425 }
426
427 config.raw_output = !strcasecmp(command,"info");
428 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
429 cliOutputHelp(--argc, ++argv);
430 return REDIS_OK;
431 }
432 if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
433 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
434 if (!strcasecmp(command,"subscribe") ||
435 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
436
437 /* Setup argument length */
438 argvlen = malloc(argc*sizeof(size_t));
439 for (j = 0; j < argc; j++)
440 argvlen[j] = sdslen(argv[j]);
441
442 while(repeat--) {
443 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
444 while (config.monitor_mode) {
445 if (cliReadReply() != REDIS_OK) exit(1);
446 fflush(stdout);
447 }
448
449 if (config.pubsub_mode) {
450 printf("Reading messages... (press Ctrl-C to quit)\n");
451 while (1) {
452 if (cliReadReply() != REDIS_OK) exit(1);
453 }
454 }
455
456 if (cliReadReply() != REDIS_OK)
457 return REDIS_ERR;
458 }
459 return REDIS_OK;
460 }
461
462 /*------------------------------------------------------------------------------
463 * User interface
464 *--------------------------------------------------------------------------- */
465
466 static int parseOptions(int argc, char **argv) {
467 int i;
468
469 for (i = 1; i < argc; i++) {
470 int lastarg = i==argc-1;
471
472 if (!strcmp(argv[i],"-h") && !lastarg) {
473 sdsfree(config.hostip);
474 config.hostip = sdsnew(argv[i+1]);
475 i++;
476 } else if (!strcmp(argv[i],"-h") && lastarg) {
477 usage();
478 } else if (!strcmp(argv[i],"--help")) {
479 usage();
480 } else if (!strcmp(argv[i],"-x")) {
481 config.stdinarg = 1;
482 } else if (!strcmp(argv[i],"-p") && !lastarg) {
483 config.hostport = atoi(argv[i+1]);
484 i++;
485 } else if (!strcmp(argv[i],"-s") && !lastarg) {
486 config.hostsocket = argv[i+1];
487 i++;
488 } else if (!strcmp(argv[i],"-r") && !lastarg) {
489 config.repeat = strtoll(argv[i+1],NULL,10);
490 i++;
491 } else if (!strcmp(argv[i],"-n") && !lastarg) {
492 config.dbnum = atoi(argv[i+1]);
493 i++;
494 } else if (!strcmp(argv[i],"-a") && !lastarg) {
495 config.auth = argv[i+1];
496 i++;
497 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
498 sds version = cliVersion();
499 printf("redis-cli %s\n", version);
500 sdsfree(version);
501 exit(0);
502 } else {
503 break;
504 }
505 }
506 return i;
507 }
508
509 static sds readArgFromStdin(void) {
510 char buf[1024];
511 sds arg = sdsempty();
512
513 while(1) {
514 int nread = read(fileno(stdin),buf,1024);
515
516 if (nread == 0) break;
517 else if (nread == -1) {
518 perror("Reading from standard input");
519 exit(1);
520 }
521 arg = sdscatlen(arg,buf,nread);
522 }
523 return arg;
524 }
525
526 static void usage() {
527 sds version = cliVersion();
528 fprintf(stderr,
529 "redis-cli %s\n"
530 "\n"
531 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
532 " -h <hostname> Server hostname (default: 127.0.0.1)\n"
533 " -p <port> Server port (default: 6379)\n"
534 " -s <socket> Server socket (overrides hostname and port)\n"
535 " -a <password> Password to use when connecting to the server\n"
536 " -r <repeat> Execute specified command N times\n"
537 " -n <db> Database number\n"
538 " -x Read last argument from STDIN\n"
539 " --help Output this help and exit\n"
540 " --version Output version and exit\n"
541 "\n"
542 "Examples:\n"
543 " cat /etc/passwd | redis-cli -x set mypasswd\n"
544 " redis-cli get mypasswd\n"
545 " redis-cli -r 100 lpush mylist x\n"
546 "\n"
547 "When no command is given, redis-cli starts in interactive mode.\n"
548 "Type \"help\" in interactive mode for information on available commands.\n"
549 "\n",
550 version);
551 sdsfree(version);
552 exit(1);
553 }
554
555 /* Turn the plain C strings into Sds strings */
556 static char **convertToSds(int count, char** args) {
557 int j;
558 char **sds = zmalloc(sizeof(char*)*count);
559
560 for(j = 0; j < count; j++)
561 sds[j] = sdsnew(args[j]);
562
563 return sds;
564 }
565
566 #define LINE_BUFLEN 4096
567 static void repl() {
568 int argc, j;
569 char *line;
570 sds *argv;
571
572 config.interactive = 1;
573 linenoiseSetCompletionCallback(completionCallback);
574
575 while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) {
576 if (line[0] != '\0') {
577 argv = sdssplitargs(line,&argc);
578 linenoiseHistoryAdd(line);
579 if (config.historyfile) linenoiseHistorySave(config.historyfile);
580 if (argv == NULL) {
581 printf("Invalid argument(s)\n");
582 continue;
583 } else if (argc > 0) {
584 if (strcasecmp(argv[0],"quit") == 0 ||
585 strcasecmp(argv[0],"exit") == 0)
586 {
587 exit(0);
588 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
589 sdsfree(config.hostip);
590 config.hostip = sdsnew(argv[1]);
591 config.hostport = atoi(argv[2]);
592 cliConnect(1);
593 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
594 linenoiseClearScreen();
595 } else {
596 long long start_time = mstime(), elapsed;
597
598 if (cliSendCommand(argc,argv,1) != REDIS_OK) {
599 cliConnect(1);
600
601 /* If we still cannot send the command,
602 * print error and abort. */
603 if (cliSendCommand(argc,argv,1) != REDIS_OK)
604 cliPrintContextErrorAndExit();
605 }
606 elapsed = mstime()-start_time;
607 if (elapsed >= 500) {
608 printf("(%.2fs)\n",(double)elapsed/1000);
609 }
610 }
611 }
612 /* Free the argument vector */
613 for (j = 0; j < argc; j++)
614 sdsfree(argv[j]);
615 zfree(argv);
616 }
617 /* linenoise() returns malloc-ed lines like readline() */
618 free(line);
619 }
620 exit(0);
621 }
622
623 static int noninteractive(int argc, char **argv) {
624 int retval = 0;
625 if (config.stdinarg) {
626 argv = zrealloc(argv, (argc+1)*sizeof(char*));
627 argv[argc] = readArgFromStdin();
628 retval = cliSendCommand(argc+1, argv, config.repeat);
629 } else {
630 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
631 retval = cliSendCommand(argc, argv, config.repeat);
632 }
633 return retval;
634 }
635
636 int main(int argc, char **argv) {
637 int firstarg;
638
639 config.hostip = sdsnew("127.0.0.1");
640 config.hostport = 6379;
641 config.hostsocket = NULL;
642 config.repeat = 1;
643 config.dbnum = 0;
644 config.interactive = 0;
645 config.shutdown = 0;
646 config.monitor_mode = 0;
647 config.pubsub_mode = 0;
648 config.raw_output = 0;
649 config.stdinarg = 0;
650 config.auth = NULL;
651 config.historyfile = NULL;
652 config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
653 config.mb_sep = '\n';
654 cliInitHelp();
655
656 if (getenv("HOME") != NULL) {
657 config.historyfile = malloc(256);
658 snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
659 linenoiseHistoryLoad(config.historyfile);
660 }
661
662 firstarg = parseOptions(argc,argv);
663 argc -= firstarg;
664 argv += firstarg;
665
666 /* Try to connect */
667 if (cliConnect(0) != REDIS_OK) exit(1);
668
669 /* Start interactive mode when no command is provided */
670 if (argc == 0) repl();
671 /* Otherwise, we have some arguments to execute */
672 return noninteractive(argc,convertToSds(argc,argv));
673 }