]> git.saurik.com Git - redis.git/blob - src/redis-cli.c
Minor update to linenoise
[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
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 void cliInitHelp() {
109 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
110 int groupslen = sizeof(commandGroups)/sizeof(char*);
111 int i, len, pos = 0;
112 helpEntry tmp;
113
114 helpEntriesLen = len = commandslen+groupslen;
115 helpEntries = malloc(sizeof(helpEntry)*len);
116
117 for (i = 0; i < groupslen; i++) {
118 tmp.argc = 1;
119 tmp.argv = malloc(sizeof(sds));
120 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
121 tmp.full = tmp.argv[0];
122 tmp.type = CLI_HELP_GROUP;
123 tmp.org = NULL;
124 helpEntries[pos++] = tmp;
125 }
126
127 for (i = 0; i < commandslen; i++) {
128 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
129 tmp.full = sdsnew(commandHelp[i].name);
130 tmp.type = CLI_HELP_COMMAND;
131 tmp.org = &commandHelp[i];
132 helpEntries[pos++] = tmp;
133 }
134 }
135
136 /* Output command help to stdout. */
137 static void cliOutputCommandHelp(struct commandHelp *help, int group) {
138 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
139 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
140 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since);
141 if (group) {
142 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
143 }
144 }
145
146 /* Print generic help. */
147 static void cliOutputGenericHelp() {
148 printf(
149 "redis-cli %s\r\n"
150 "Type: \"help @<group>\" to get a list of commands in <group>\r\n"
151 " \"help <command>\" for help on <command>\r\n"
152 " \"help <tab>\" to get a list of possible help topics\r\n"
153 " \"quit\" to exit\r\n",
154 REDIS_VERSION
155 );
156 }
157
158 /* Output all command help, filtering by group or command name. */
159 static void cliOutputHelp(int argc, char **argv) {
160 int i, j, len;
161 int group = -1;
162 helpEntry *entry;
163 struct commandHelp *help;
164
165 if (argc == 0) {
166 cliOutputGenericHelp();
167 return;
168 } else if (argc > 0 && argv[0][0] == '@') {
169 len = sizeof(commandGroups)/sizeof(char*);
170 for (i = 0; i < len; i++) {
171 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
172 group = i;
173 break;
174 }
175 }
176 }
177
178 assert(argc > 0);
179 for (i = 0; i < helpEntriesLen; i++) {
180 entry = &helpEntries[i];
181 if (entry->type != CLI_HELP_COMMAND) continue;
182
183 help = entry->org;
184 if (group == -1) {
185 /* Compare all arguments */
186 if (argc == entry->argc) {
187 for (j = 0; j < argc; j++) {
188 if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
189 }
190 if (j == argc) {
191 cliOutputCommandHelp(help,1);
192 }
193 }
194 } else {
195 if (group == help->group) {
196 cliOutputCommandHelp(help,0);
197 }
198 }
199 }
200 printf("\r\n");
201 }
202
203 static void completionCallback(const char *buf, linenoiseCompletions *lc) {
204 size_t startpos = 0;
205 int mask;
206 int i;
207 size_t matchlen;
208 sds tmp;
209
210 if (strncasecmp(buf,"help ",5) == 0) {
211 startpos = 5;
212 while (isspace(buf[startpos])) startpos++;
213 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
214 } else {
215 mask = CLI_HELP_COMMAND;
216 }
217
218 for (i = 0; i < helpEntriesLen; i++) {
219 if (!(helpEntries[i].type & mask)) continue;
220
221 matchlen = strlen(buf+startpos);
222 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
223 tmp = sdsnewlen(buf,startpos);
224 tmp = sdscat(tmp,helpEntries[i].full);
225 linenoiseAddCompletion(lc,tmp);
226 sdsfree(tmp);
227 }
228 }
229 }
230
231 /*------------------------------------------------------------------------------
232 * Networking / parsing
233 *--------------------------------------------------------------------------- */
234
235 /* Send AUTH command to the server */
236 static int cliAuth() {
237 redisReply *reply;
238 if (config.auth == NULL) return REDIS_OK;
239
240 reply = redisCommand(context,"AUTH %s",config.auth);
241 if (reply != NULL) {
242 freeReplyObject(reply);
243 return REDIS_OK;
244 }
245 return REDIS_ERR;
246 }
247
248 /* Send SELECT dbnum to the server */
249 static int cliSelect() {
250 redisReply *reply;
251 char dbnum[16];
252 if (config.dbnum == 0) return REDIS_OK;
253
254 snprintf(dbnum,sizeof(dbnum),"%d",config.dbnum);
255 reply = redisCommand(context,"SELECT %s",dbnum);
256 if (reply != NULL) {
257 freeReplyObject(reply);
258 return REDIS_OK;
259 }
260 return REDIS_ERR;
261 }
262
263 /* Connect to the client. If force is not zero the connection is performed
264 * even if there is already a connected socket. */
265 static int cliConnect(int force) {
266 if (context == NULL || force) {
267 if (context != NULL)
268 redisFree(context);
269
270 if (config.hostsocket == NULL) {
271 context = redisConnect(config.hostip,config.hostport);
272 } else {
273 context = redisConnectUnix(config.hostsocket);
274 }
275
276 if (context->err) {
277 fprintf(stderr,"Could not connect to Redis at ");
278 if (config.hostsocket == NULL)
279 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
280 else
281 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
282 redisFree(context);
283 context = NULL;
284 return REDIS_ERR;
285 }
286
287 /* Do AUTH and select the right DB. */
288 if (cliAuth() != REDIS_OK)
289 return REDIS_ERR;
290 if (cliSelect() != REDIS_OK)
291 return REDIS_ERR;
292 }
293 return REDIS_OK;
294 }
295
296 static void cliPrintContextErrorAndExit() {
297 if (context == NULL) return;
298 fprintf(stderr,"Error: %s\n",context->errstr);
299 exit(1);
300 }
301
302 static sds cliFormatReply(redisReply *r, char *prefix) {
303 sds out = sdsempty();
304 switch (r->type) {
305 case REDIS_REPLY_ERROR:
306 if (config.tty) out = sdscat(out,"(error) ");
307 out = sdscatprintf(out,"%s\n", r->str);
308 break;
309 case REDIS_REPLY_STATUS:
310 out = sdscat(out,r->str);
311 out = sdscat(out,"\n");
312 break;
313 case REDIS_REPLY_INTEGER:
314 if (config.tty) out = sdscat(out,"(integer) ");
315 out = sdscatprintf(out,"%lld\n",r->integer);
316 break;
317 case REDIS_REPLY_STRING:
318 if (config.raw_output || !config.tty) {
319 out = sdscatlen(out,r->str,r->len);
320 } else {
321 /* If you are producing output for the standard output we want
322 * a more interesting output with quoted characters and so forth */
323 out = sdscatrepr(out,r->str,r->len);
324 out = sdscat(out,"\n");
325 }
326 break;
327 case REDIS_REPLY_NIL:
328 out = sdscat(out,"(nil)\n");
329 break;
330 case REDIS_REPLY_ARRAY:
331 if (r->elements == 0) {
332 out = sdscat(out,"(empty list or set)\n");
333 } else {
334 unsigned int i, idxlen = 0;
335 char _prefixlen[16];
336 char _prefixfmt[16];
337 sds _prefix;
338 sds tmp;
339
340 /* Calculate chars needed to represent the largest index */
341 i = r->elements;
342 do {
343 idxlen++;
344 i /= 10;
345 } while(i);
346
347 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
348 memset(_prefixlen,' ',idxlen+2);
349 _prefixlen[idxlen+2] = '\0';
350 _prefix = sdscat(sdsnew(prefix),_prefixlen);
351
352 /* Setup prefix format for every entry */
353 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);
354
355 for (i = 0; i < r->elements; i++) {
356 /* Don't use the prefix for the first element, as the parent
357 * caller already prepended the index number. */
358 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
359
360 /* Format the multi bulk entry */
361 tmp = cliFormatReply(r->element[i],_prefix);
362 out = sdscatlen(out,tmp,sdslen(tmp));
363 sdsfree(tmp);
364 }
365 sdsfree(_prefix);
366 }
367 break;
368 default:
369 fprintf(stderr,"Unknown reply type: %d\n", r->type);
370 exit(1);
371 }
372 return out;
373 }
374
375 static int cliReadReply() {
376 redisReply *reply;
377 sds out;
378
379 if (redisGetReply(context,(void**)&reply) != REDIS_OK) {
380 if (config.shutdown)
381 return REDIS_OK;
382 if (config.interactive) {
383 /* Filter cases where we should reconnect */
384 if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
385 return REDIS_ERR;
386 if (context->err == REDIS_ERR_EOF)
387 return REDIS_ERR;
388 }
389 cliPrintContextErrorAndExit();
390 return REDIS_ERR; /* avoid compiler warning */
391 }
392
393 out = cliFormatReply(reply,"");
394 freeReplyObject(reply);
395 fwrite(out,sdslen(out),1,stdout);
396 sdsfree(out);
397 return REDIS_OK;
398 }
399
400 static int cliSendCommand(int argc, char **argv, int repeat) {
401 char *command = argv[0];
402 size_t *argvlen;
403 int j;
404
405 config.raw_output = !strcasecmp(command,"info");
406 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
407 cliOutputHelp(--argc, ++argv);
408 return REDIS_OK;
409 }
410 if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
411 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
412 if (!strcasecmp(command,"subscribe") ||
413 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
414
415 /* Setup argument length */
416 argvlen = malloc(argc*sizeof(size_t));
417 for (j = 0; j < argc; j++)
418 argvlen[j] = sdslen(argv[j]);
419
420 while(repeat--) {
421 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
422 while (config.monitor_mode) {
423 if (cliReadReply() != REDIS_OK) exit(1);
424 fflush(stdout);
425 }
426
427 if (config.pubsub_mode) {
428 printf("Reading messages... (press Ctrl-C to quit)\n");
429 while (1) {
430 if (cliReadReply() != REDIS_OK) exit(1);
431 }
432 }
433
434 if (cliReadReply() != REDIS_OK)
435 return REDIS_ERR;
436 }
437 return REDIS_OK;
438 }
439
440 /*------------------------------------------------------------------------------
441 * User interface
442 *--------------------------------------------------------------------------- */
443
444 static int parseOptions(int argc, char **argv) {
445 int i;
446
447 for (i = 1; i < argc; i++) {
448 int lastarg = i==argc-1;
449
450 if (!strcmp(argv[i],"-h") && !lastarg) {
451 config.hostip = argv[i+1];
452 i++;
453 } else if (!strcmp(argv[i],"-h") && lastarg) {
454 usage();
455 } else if (!strcmp(argv[i],"-x")) {
456 config.stdinarg = 1;
457 } else if (!strcmp(argv[i],"-p") && !lastarg) {
458 config.hostport = atoi(argv[i+1]);
459 i++;
460 } else if (!strcmp(argv[i],"-s") && !lastarg) {
461 config.hostsocket = argv[i+1];
462 i++;
463 } else if (!strcmp(argv[i],"-r") && !lastarg) {
464 config.repeat = strtoll(argv[i+1],NULL,10);
465 i++;
466 } else if (!strcmp(argv[i],"-n") && !lastarg) {
467 config.dbnum = atoi(argv[i+1]);
468 i++;
469 } else if (!strcmp(argv[i],"-a") && !lastarg) {
470 config.auth = argv[i+1];
471 i++;
472 } else if (!strcmp(argv[i],"-i")) {
473 fprintf(stderr,
474 "Starting interactive mode using -i is deprecated. Interactive mode is started\n"
475 "by default when redis-cli is executed without a command to execute.\n"
476 );
477 } else if (!strcmp(argv[i],"-c")) {
478 fprintf(stderr,
479 "Reading last argument from standard input using -c is deprecated.\n"
480 "When standard input is connected to a pipe or regular file, it is\n"
481 "automatically used as last argument.\n"
482 );
483 } else if (!strcmp(argv[i],"-v")) {
484 printf("redis-cli shipped with Redis version %s (%s)\n", REDIS_VERSION, redisGitSHA1());
485 exit(0);
486 } else {
487 break;
488 }
489 }
490 return i;
491 }
492
493 static sds readArgFromStdin(void) {
494 char buf[1024];
495 sds arg = sdsempty();
496
497 while(1) {
498 int nread = read(fileno(stdin),buf,1024);
499
500 if (nread == 0) break;
501 else if (nread == -1) {
502 perror("Reading from standard input");
503 exit(1);
504 }
505 arg = sdscatlen(arg,buf,nread);
506 }
507 return arg;
508 }
509
510 static void usage() {
511 fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-s /path/to/socket] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
512 fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
513 fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
514 fprintf(stderr, "example: redis-cli get my_passwd\n");
515 fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
516 fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
517 exit(1);
518 }
519
520 /* Turn the plain C strings into Sds strings */
521 static char **convertToSds(int count, char** args) {
522 int j;
523 char **sds = zmalloc(sizeof(char*)*count);
524
525 for(j = 0; j < count; j++)
526 sds[j] = sdsnew(args[j]);
527
528 return sds;
529 }
530
531 #define LINE_BUFLEN 4096
532 static void repl() {
533 int argc, j;
534 char *line;
535 sds *argv;
536
537 config.interactive = 1;
538 linenoiseSetCompletionCallback(completionCallback);
539 while((line = linenoise("redis> ")) != NULL) {
540 if (line[0] != '\0') {
541 argv = sdssplitargs(line,&argc);
542 linenoiseHistoryAdd(line);
543 if (config.historyfile) linenoiseHistorySave(config.historyfile);
544 if (argv == NULL) {
545 printf("Invalid argument(s)\n");
546 continue;
547 } else if (argc > 0) {
548 if (strcasecmp(argv[0],"quit") == 0 ||
549 strcasecmp(argv[0],"exit") == 0)
550 {
551 exit(0);
552 } else {
553 long long start_time = mstime(), elapsed;
554
555 if (cliSendCommand(argc,argv,1) != REDIS_OK) {
556 printf("Reconnecting... ");
557 fflush(stdout);
558 if (cliConnect(1) != REDIS_OK) exit(1);
559 printf("OK\n");
560
561 /* If we still cannot send the command,
562 * print error and abort. */
563 if (cliSendCommand(argc,argv,1) != REDIS_OK)
564 cliPrintContextErrorAndExit();
565 }
566 elapsed = mstime()-start_time;
567 if (elapsed >= 500) {
568 printf("(%.2fs)\n",(double)elapsed/1000);
569 }
570 }
571 }
572 /* Free the argument vector */
573 for (j = 0; j < argc; j++)
574 sdsfree(argv[j]);
575 zfree(argv);
576 }
577 /* linenoise() returns malloc-ed lines like readline() */
578 free(line);
579 }
580 exit(0);
581 }
582
583 static int noninteractive(int argc, char **argv) {
584 int retval = 0;
585 if (config.stdinarg) {
586 argv = zrealloc(argv, (argc+1)*sizeof(char*));
587 argv[argc] = readArgFromStdin();
588 retval = cliSendCommand(argc+1, argv, config.repeat);
589 } else {
590 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
591 retval = cliSendCommand(argc, argv, config.repeat);
592 }
593 return retval;
594 }
595
596 int main(int argc, char **argv) {
597 int firstarg;
598
599 config.hostip = "127.0.0.1";
600 config.hostport = 6379;
601 config.hostsocket = NULL;
602 config.repeat = 1;
603 config.dbnum = 0;
604 config.interactive = 0;
605 config.shutdown = 0;
606 config.monitor_mode = 0;
607 config.pubsub_mode = 0;
608 config.raw_output = 0;
609 config.stdinarg = 0;
610 config.auth = NULL;
611 config.historyfile = NULL;
612 config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
613 config.mb_sep = '\n';
614 cliInitHelp();
615
616 if (getenv("HOME") != NULL) {
617 config.historyfile = malloc(256);
618 snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
619 linenoiseHistoryLoad(config.historyfile);
620 }
621
622 firstarg = parseOptions(argc,argv);
623 argc -= firstarg;
624 argv += firstarg;
625
626 /* Try to connect */
627 if (cliConnect(0) != REDIS_OK) exit(1);
628
629 /* Start interactive mode when no command is provided */
630 if (argc == 0) repl();
631 /* Otherwise, we have some arguments to execute */
632 return noninteractive(argc,convertToSds(argc,argv));
633 }