]> git.saurik.com Git - redis.git/blob - src/redis-cli.c
1d00fb87398446ff51419165eaaabe3c9de58bdf
[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
40 #include "anet.h"
41 #include "sds.h"
42 #include "adlist.h"
43 #include "zmalloc.h"
44 #include "linenoise.h"
45
46 #define REDIS_CMD_INLINE 1
47 #define REDIS_CMD_BULK 2
48 #define REDIS_CMD_MULTIBULK 4
49
50 #define REDIS_NOTUSED(V) ((void) V)
51
52 static struct config {
53 char *hostip;
54 int hostport;
55 long repeat;
56 int dbnum;
57 int argn_from_stdin;
58 int interactive;
59 int shutdown;
60 int monitor_mode;
61 int pubsub_mode;
62 int raw_output;
63 char *auth;
64 } config;
65
66 static int cliReadReply(int fd);
67 static void usage();
68
69 static int cliConnect(void) {
70 char err[ANET_ERR_LEN];
71 static int fd = ANET_ERR;
72
73 if (fd == ANET_ERR) {
74 fd = anetTcpConnect(err,config.hostip,config.hostport);
75 if (fd == ANET_ERR) {
76 fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
77 return -1;
78 }
79 anetTcpNoDelay(NULL,fd);
80 }
81 return fd;
82 }
83
84 static sds cliReadLine(int fd) {
85 sds line = sdsempty();
86
87 while(1) {
88 char c;
89 ssize_t ret;
90
91 ret = read(fd,&c,1);
92 if (ret == -1) {
93 sdsfree(line);
94 return NULL;
95 } else if ((ret == 0) || (c == '\n')) {
96 break;
97 } else {
98 line = sdscatlen(line,&c,1);
99 }
100 }
101 return sdstrim(line,"\r\n");
102 }
103
104 static int cliReadSingleLineReply(int fd, int quiet) {
105 sds reply = cliReadLine(fd);
106
107 if (reply == NULL) return 1;
108 if (!quiet)
109 printf("%s\n", reply);
110 sdsfree(reply);
111 return 0;
112 }
113
114 static void printStringRepr(char *s, int len) {
115 printf("\"");
116 while(len--) {
117 switch(*s) {
118 case '\\':
119 case '"':
120 printf("\\%c",*s);
121 break;
122 case '\n': printf("\\n"); break;
123 case '\r': printf("\\r"); break;
124 case '\t': printf("\\t"); break;
125 case '\a': printf("\\a"); break;
126 case '\b': printf("\\b"); break;
127 default:
128 if (isprint(*s))
129 printf("%c",*s);
130 else
131 printf("\\x%02x",(unsigned char)*s);
132 break;
133 }
134 s++;
135 }
136 printf("\"\n");
137 }
138
139 static int cliReadBulkReply(int fd) {
140 sds replylen = cliReadLine(fd);
141 char *reply, crlf[2];
142 int bulklen;
143
144 if (replylen == NULL) return 1;
145 bulklen = atoi(replylen);
146 if (bulklen == -1) {
147 sdsfree(replylen);
148 printf("(nil)\n");
149 return 0;
150 }
151 reply = zmalloc(bulklen);
152 anetRead(fd,reply,bulklen);
153 anetRead(fd,crlf,2);
154 if (config.raw_output || !isatty(fileno(stdout))) {
155 if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
156 zfree(reply);
157 return 1;
158 }
159 } else {
160 /* If you are producing output for the standard output we want
161 * a more interesting output with quoted characters and so forth */
162 printStringRepr(reply,bulklen);
163 }
164 zfree(reply);
165 return 0;
166 }
167
168 static int cliReadMultiBulkReply(int fd) {
169 sds replylen = cliReadLine(fd);
170 int elements, c = 1;
171
172 if (replylen == NULL) return 1;
173 elements = atoi(replylen);
174 if (elements == -1) {
175 sdsfree(replylen);
176 printf("(nil)\n");
177 return 0;
178 }
179 if (elements == 0) {
180 printf("(empty list or set)\n");
181 }
182 while(elements--) {
183 printf("%d. ", c);
184 if (cliReadReply(fd)) return 1;
185 c++;
186 }
187 return 0;
188 }
189
190 static int cliReadReply(int fd) {
191 char type;
192
193 if (anetRead(fd,&type,1) <= 0) {
194 if (config.shutdown) return 0;
195 exit(1);
196 }
197 switch(type) {
198 case '-':
199 printf("(error) ");
200 cliReadSingleLineReply(fd,0);
201 return 1;
202 case '+':
203 return cliReadSingleLineReply(fd,0);
204 case ':':
205 printf("(integer) ");
206 return cliReadSingleLineReply(fd,0);
207 case '$':
208 return cliReadBulkReply(fd);
209 case '*':
210 return cliReadMultiBulkReply(fd);
211 default:
212 printf("protocol error, got '%c' as reply type byte\n", type);
213 return 1;
214 }
215 }
216
217 static int selectDb(int fd) {
218 int retval;
219 sds cmd;
220 char type;
221
222 if (config.dbnum == 0)
223 return 0;
224
225 cmd = sdsempty();
226 cmd = sdscatprintf(cmd,"SELECT %d\r\n",config.dbnum);
227 anetWrite(fd,cmd,sdslen(cmd));
228 anetRead(fd,&type,1);
229 if (type <= 0 || type != '+') return 1;
230 retval = cliReadSingleLineReply(fd,1);
231 if (retval) {
232 return retval;
233 }
234 return 0;
235 }
236
237 static int cliSendCommand(int argc, char **argv, int repeat) {
238 char *command = argv[0];
239 int fd, j, retval = 0;
240 sds cmd;
241
242 config.raw_output = !strcasecmp(command,"info");
243 if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
244 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
245 if (!strcasecmp(command,"subscribe") ||
246 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
247 if ((fd = cliConnect()) == -1) return 1;
248
249 /* Select db number */
250 retval = selectDb(fd);
251 if (retval) {
252 fprintf(stderr,"Error setting DB num\n");
253 return 1;
254 }
255
256 /* Build the command to send */
257 cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc);
258 for (j = 0; j < argc; j++) {
259 cmd = sdscatprintf(cmd,"$%lu\r\n",
260 (unsigned long)sdslen(argv[j]));
261 cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
262 cmd = sdscatlen(cmd,"\r\n",2);
263 }
264
265 while(repeat--) {
266 anetWrite(fd,cmd,sdslen(cmd));
267 while (config.monitor_mode) {
268 cliReadSingleLineReply(fd,0);
269 }
270
271 if (config.pubsub_mode) {
272 printf("Reading messages... (press Ctrl-c to quit)\n");
273 while (1) {
274 cliReadReply(fd);
275 printf("\n");
276 }
277 }
278
279 retval = cliReadReply(fd);
280 if (retval) {
281 return retval;
282 }
283 }
284 return 0;
285 }
286
287 static int parseOptions(int argc, char **argv) {
288 int i;
289
290 for (i = 1; i < argc; i++) {
291 int lastarg = i==argc-1;
292
293 if (!strcmp(argv[i],"-h") && !lastarg) {
294 char *ip = zmalloc(32);
295 if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
296 printf("Can't resolve %s\n", argv[i]);
297 exit(1);
298 }
299 config.hostip = ip;
300 i++;
301 } else if (!strcmp(argv[i],"-h") && lastarg) {
302 usage();
303 } else if (!strcmp(argv[i],"-p") && !lastarg) {
304 config.hostport = atoi(argv[i+1]);
305 i++;
306 } else if (!strcmp(argv[i],"-r") && !lastarg) {
307 config.repeat = strtoll(argv[i+1],NULL,10);
308 i++;
309 } else if (!strcmp(argv[i],"-n") && !lastarg) {
310 config.dbnum = atoi(argv[i+1]);
311 i++;
312 } else if (!strcmp(argv[i],"-a") && !lastarg) {
313 config.auth = argv[i+1];
314 i++;
315 } else if (!strcmp(argv[i],"-i")) {
316 config.interactive = 1;
317 } else if (!strcmp(argv[i],"-c")) {
318 config.argn_from_stdin = 1;
319 } else if (!strcmp(argv[i],"-v")) {
320 printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION);
321 exit(0);
322 } else {
323 break;
324 }
325 }
326 return i;
327 }
328
329 static sds readArgFromStdin(void) {
330 char buf[1024];
331 sds arg = sdsempty();
332
333 while(1) {
334 int nread = read(fileno(stdin),buf,1024);
335
336 if (nread == 0) break;
337 else if (nread == -1) {
338 perror("Reading from standard input");
339 exit(1);
340 }
341 arg = sdscatlen(arg,buf,nread);
342 }
343 return arg;
344 }
345
346 static void usage() {
347 fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
348 fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
349 fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
350 fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
351 fprintf(stderr, "example: redis-cli get my_passwd\n");
352 fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
353 fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
354 exit(1);
355 }
356
357 /* Turn the plain C strings into Sds strings */
358 static char **convertToSds(int count, char** args) {
359 int j;
360 char **sds = zmalloc(sizeof(char*)*count);
361
362 for(j = 0; j < count; j++)
363 sds[j] = sdsnew(args[j]);
364
365 return sds;
366 }
367
368 static char **splitArguments(char *line, int *argc) {
369 char *p = line;
370 char *current = NULL;
371 char **vector = NULL;
372
373 *argc = 0;
374 while(1) {
375 /* skip blanks */
376 while(*p && isspace(*p)) p++;
377 if (*p) {
378 /* get a token */
379 int inq=0; /* set to 1 if we are in "quotes" */
380 int done = 0;
381
382 if (current == NULL) current = sdsempty();
383 while(!done) {
384 if (inq) {
385 if (*p == '\\' && *(p+1)) {
386 char c;
387
388 p++;
389 switch(*p) {
390 case 'n': c = '\n'; break;
391 case 'r': c = '\r'; break;
392 case 't': c = '\t'; break;
393 case 'b': c = '\b'; break;
394 case 'a': c = '\a'; break;
395 default: c = *p; break;
396 }
397 current = sdscatlen(current,&c,1);
398 } else if (*p == '"') {
399 done = 1;
400 } else {
401 current = sdscatlen(current,p,1);
402 }
403 } else {
404 switch(*p) {
405 case ' ':
406 case '\n':
407 case '\r':
408 case '\t':
409 case '\0':
410 done=1;
411 break;
412 case '"':
413 inq=1;
414 break;
415 default:
416 current = sdscatlen(current,p,1);
417 break;
418 }
419 }
420 if (*p) p++;
421 }
422 /* add the token to the vector */
423 vector = zrealloc(vector,((*argc)+1)*sizeof(char*));
424 vector[*argc] = current;
425 (*argc)++;
426 current = NULL;
427 } else {
428 return vector;
429 }
430 }
431 }
432
433 #define LINE_BUFLEN 4096
434 static void repl() {
435 int argc, j;
436 char *line, **argv;
437
438 while((line = linenoise("redis> ")) != NULL) {
439 if (line[0] != '\0') {
440 argv = splitArguments(line,&argc);
441 linenoiseHistoryAdd(line);
442 if (argc > 0) {
443 if (strcasecmp(argv[0],"quit") == 0 ||
444 strcasecmp(argv[0],"exit") == 0)
445 exit(0);
446 else
447 cliSendCommand(argc, argv, 1);
448 }
449 /* Free the argument vector */
450 for (j = 0; j < argc; j++)
451 sdsfree(argv[j]);
452 zfree(argv);
453 }
454 /* linenoise() returns malloc-ed lines like readline() */
455 free(line);
456 }
457 exit(0);
458 }
459
460 int main(int argc, char **argv) {
461 int firstarg;
462 char **argvcopy;
463
464 config.hostip = "127.0.0.1";
465 config.hostport = 6379;
466 config.repeat = 1;
467 config.dbnum = 0;
468 config.argn_from_stdin = 0;
469 config.shutdown = 0;
470 config.interactive = 0;
471 config.monitor_mode = 0;
472 config.pubsub_mode = 0;
473 config.raw_output = 0;
474 config.auth = NULL;
475
476 firstarg = parseOptions(argc,argv);
477 argc -= firstarg;
478 argv += firstarg;
479
480 if (config.auth != NULL) {
481 char *authargv[2];
482
483 authargv[0] = "AUTH";
484 authargv[1] = config.auth;
485 cliSendCommand(2, convertToSds(2, authargv), 1);
486 }
487
488 if (argc == 0 || config.interactive == 1) repl();
489
490 argvcopy = convertToSds(argc+1, argv);
491 if (config.argn_from_stdin) {
492 sds lastarg = readArgFromStdin();
493 argvcopy[argc] = lastarg;
494 argc++;
495 }
496 return cliSendCommand(argc, argvcopy, config.repeat);
497 }