redis-cli.c 15.0 KB
Newer Older
A
antirez 已提交
1 2
/* Redis CLI (command line interface)
 *
3
 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
A
antirez 已提交
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

31
#include "fmacros.h"
32
#include "version.h"
33

A
antirez 已提交
34 35 36 37
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
38
#include <ctype.h>
39
#include <errno.h>
40
#include <sys/stat.h>
A
antirez 已提交
41 42 43 44 45

#include "anet.h"
#include "sds.h"
#include "adlist.h"
#include "zmalloc.h"
46
#include "linenoise.h"
A
antirez 已提交
47 48 49 50 51 52

#define REDIS_NOTUSED(V) ((void) V)

static struct config {
    char *hostip;
    int hostport;
53
    long repeat;
I
ian 已提交
54
    int dbnum;
55
    int interactive;
56
    int shutdown;
57 58
    int monitor_mode;
    int pubsub_mode;
59
    int raw_output; /* output mode per command */
60
    int tty; /* flag for default output format */
61
    int stdinarg; /* get last arg from stdin. (-x option) */
62
    char mb_sep;
A
antirez 已提交
63
    char *auth;
64
    char *historyfile;
A
antirez 已提交
65 66
} config;

67
static int cliReadReply(int fd);
A
antirez 已提交
68
static void usage();
69

70 71 72
/* Connect to the client. If force is not zero the connection is performed
 * even if there is already a connected socket. */
static int cliConnect(int force) {
A
antirez 已提交
73
    char err[ANET_ERR_LEN];
74
    static int fd = ANET_ERR;
A
antirez 已提交
75

76 77
    if (fd == ANET_ERR || force) {
        if (force) close(fd);
78 79 80 81 82 83
        fd = anetTcpConnect(err,config.hostip,config.hostport);
        if (fd == ANET_ERR) {
            fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
            return -1;
        }
        anetTcpNoDelay(NULL,fd);
A
antirez 已提交
84 85 86 87 88 89 90 91 92
    }
    return fd;
}

static sds cliReadLine(int fd) {
    sds line = sdsempty();

    while(1) {
        char c;
93
        ssize_t ret;
A
antirez 已提交
94

95
        ret = read(fd,&c,1);
A
antirez 已提交
96
        if (ret <= 0) {
A
antirez 已提交
97 98
            sdsfree(line);
            return NULL;
99
        } else if ((ret == 0) || (c == '\n')) {
A
antirez 已提交
100 101 102 103 104 105 106 107
            break;
        } else {
            line = sdscatlen(line,&c,1);
        }
    }
    return sdstrim(line,"\r\n");
}

I
ian 已提交
108
static int cliReadSingleLineReply(int fd, int quiet) {
A
antirez 已提交
109 110 111
    sds reply = cliReadLine(fd);

    if (reply == NULL) return 1;
I
ian 已提交
112
    if (!quiet)
113
        printf("%s", reply);
114
    sdsfree(reply);
A
antirez 已提交
115 116 117
    return 0;
}

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
static void printStringRepr(char *s, int len) {
    printf("\"");
    while(len--) {
        switch(*s) {
        case '\\':
        case '"':
            printf("\\%c",*s);
            break;
        case '\n': printf("\\n"); break;
        case '\r': printf("\\r"); break;
        case '\t': printf("\\t"); break;
        case '\a': printf("\\a"); break;
        case '\b': printf("\\b"); break;
        default:
            if (isprint(*s))
                printf("%c",*s);
            else
                printf("\\x%02x",(unsigned char)*s);
            break;
        }
        s++;
    }
140
    printf("\"");
141 142
}

143
static int cliReadBulkReply(int fd) {
A
antirez 已提交
144 145
    sds replylen = cliReadLine(fd);
    char *reply, crlf[2];
146
    int bulklen;
A
antirez 已提交
147 148 149

    if (replylen == NULL) return 1;
    bulklen = atoi(replylen);
150
    if (bulklen == -1) {
A
antirez 已提交
151
        sdsfree(replylen);
152
        printf("(nil)\n");
A
antirez 已提交
153 154 155 156 157
        return 0;
    }
    reply = zmalloc(bulklen);
    anetRead(fd,reply,bulklen);
    anetRead(fd,crlf,2);
158
    if (config.raw_output || !config.tty) {
159 160 161 162 163 164 165 166
        if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
            zfree(reply);
            return 1;
        }
    } else {
        /* If you are producing output for the standard output we want
         * a more interesting output with quoted characters and so forth */
        printStringRepr(reply,bulklen);
A
antirez 已提交
167 168
    }
    zfree(reply);
169
    return 0;
A
antirez 已提交
170 171 172 173 174
}

static int cliReadMultiBulkReply(int fd) {
    sds replylen = cliReadLine(fd);
    int elements, c = 1;
175
    int retval = 0;
A
antirez 已提交
176 177

    if (replylen == NULL) return 1;
178 179
    elements = atoi(replylen);
    if (elements == -1) {
A
antirez 已提交
180 181 182 183
        sdsfree(replylen);
        printf("(nil)\n");
        return 0;
    }
184 185 186
    if (elements == 0) {
        printf("(empty list or set)\n");
    }
A
antirez 已提交
187
    while(elements--) {
188
        if (config.tty) printf("%d. ", c);
189
        if (cliReadReply(fd)) retval = 1;
190
        if (elements) printf("%c",config.mb_sep);
A
antirez 已提交
191 192
        c++;
    }
193
    return retval;
A
antirez 已提交
194 195
}

196 197
static int cliReadReply(int fd) {
    char type;
198
    int nread;
199

200
    if ((nread = anetRead(fd,&type,1)) <= 0) {
201
        if (config.shutdown) return 0;
202 203 204 205 206 207 208 209
        if (config.interactive &&
            (nread == 0 || (nread == -1 && errno == ECONNRESET)))
        {
            return ECONNRESET;
        } else {
            printf("I/O error while reading from socket: %s",strerror(errno));
            exit(1);
        }
210
    }
211 212
    switch(type) {
    case '-':
213
        if (config.tty) printf("(error) ");
I
ian 已提交
214
        cliReadSingleLineReply(fd,0);
215 216
        return 1;
    case '+':
I
ian 已提交
217
        return cliReadSingleLineReply(fd,0);
218
    case ':':
219
        if (config.tty) printf("(integer) ");
I
ian 已提交
220
        return cliReadSingleLineReply(fd,0);
221 222 223 224 225
    case '$':
        return cliReadBulkReply(fd);
    case '*':
        return cliReadMultiBulkReply(fd);
    default:
226
        printf("protocol error, got '%c' as reply type byte", type);
227 228 229 230
        return 1;
    }
}

231
static int selectDb(int fd) {
I
ian 已提交
232 233 234 235 236 237 238 239 240 241 242 243 244 245
    int retval;
    sds cmd;
    char type;

    if (config.dbnum == 0)
        return 0;

    cmd = sdsempty();
    cmd = sdscatprintf(cmd,"SELECT %d\r\n",config.dbnum);
    anetWrite(fd,cmd,sdslen(cmd));
    anetRead(fd,&type,1);
    if (type <= 0 || type != '+') return 1;
    retval = cliReadSingleLineReply(fd,1);
    if (retval) {
246
        return retval;
I
ian 已提交
247 248 249 250
    }
    return 0;
}

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
static void showInteractiveHelp(void) {
    printf(
    "\n"
    "Welcome to redis-cli " REDIS_VERSION "!\n"
    "Just type any valid Redis command to see a pretty printed output.\n"
    "\n"
    "It is possible to quote strings, like in:\n"
    "  set \"my key\" \"some string \\xff\\n\"\n"
    "\n"
    "You can find a list of valid Redis commands at\n"
    "  http://code.google.com/p/redis/wiki/CommandReference\n"
    "\n"
    "Note: redis-cli supports line editing, use up/down arrows for history."
    "\n\n");
}

267
static int cliSendCommand(int argc, char **argv, int repeat) {
268
    char *command = argv[0];
A
antirez 已提交
269
    int fd, j, retval = 0;
270
    sds cmd;
A
antirez 已提交
271

272
    config.raw_output = !strcasecmp(command,"info");
273 274 275 276
    if (!strcasecmp(command,"help")) {
        showInteractiveHelp();
        return 0;
    }
277 278 279 280
    if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
    if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
    if (!strcasecmp(command,"subscribe") ||
        !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
281
    if ((fd = cliConnect(0)) == -1) return 1;
A
antirez 已提交
282

I
ian 已提交
283 284 285 286 287 288
    /* Select db number */
    retval = selectDb(fd);
    if (retval) {
        fprintf(stderr,"Error setting DB num\n");
        return 1;
    }
289

290 291 292 293 294 295 296 297 298
    /* Build the command to send */
    cmd = sdscatprintf(sdsempty(),"*%d\r\n",argc);
    for (j = 0; j < argc; j++) {
        cmd = sdscatprintf(cmd,"$%lu\r\n",
            (unsigned long)sdslen(argv[j]));
        cmd = sdscatlen(cmd,argv[j],sdslen(argv[j]));
        cmd = sdscatlen(cmd,"\r\n",2);
    }

299
    while(repeat--) {
300
        anetWrite(fd,cmd,sdslen(cmd));
301
        while (config.monitor_mode) {
A
antirez 已提交
302 303
            if (cliReadSingleLineReply(fd,0)) exit(1);
            printf("\n");
304 305
        }

306 307 308 309
        if (config.pubsub_mode) {
            printf("Reading messages... (press Ctrl-c to quit)\n");
            while (1) {
                cliReadReply(fd);
310
                printf("\n\n");
311 312 313
            }
        }

314
        retval = cliReadReply(fd);
315 316
        if (!config.raw_output && config.tty) printf("\n");
        if (retval) return retval;
A
antirez 已提交
317 318 319 320 321 322 323 324 325
    }
    return 0;
}

static int parseOptions(int argc, char **argv) {
    int i;

    for (i = 1; i < argc; i++) {
        int lastarg = i==argc-1;
326

A
antirez 已提交
327 328 329 330 331 332 333 334
        if (!strcmp(argv[i],"-h") && !lastarg) {
            char *ip = zmalloc(32);
            if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
                printf("Can't resolve %s\n", argv[i]);
                exit(1);
            }
            config.hostip = ip;
            i++;
A
antirez 已提交
335 336
        } else if (!strcmp(argv[i],"-h") && lastarg) {
            usage();
337 338
        } else if (!strcmp(argv[i],"-x")) {
            config.stdinarg = 1;
A
antirez 已提交
339 340 341
        } else if (!strcmp(argv[i],"-p") && !lastarg) {
            config.hostport = atoi(argv[i+1]);
            i++;
342 343 344
        } else if (!strcmp(argv[i],"-r") && !lastarg) {
            config.repeat = strtoll(argv[i+1],NULL,10);
            i++;
I
ian 已提交
345 346 347
        } else if (!strcmp(argv[i],"-n") && !lastarg) {
            config.dbnum = atoi(argv[i+1]);
            i++;
348
        } else if (!strcmp(argv[i],"-a") && !lastarg) {
A
antirez 已提交
349
            config.auth = argv[i+1];
350
            i++;
351
        } else if (!strcmp(argv[i],"-i")) {
352 353 354 355
            fprintf(stderr,
"Starting interactive mode using -i is deprecated. Interactive mode is started\n"
"by default when redis-cli is executed without a command to execute.\n"
            );
356
        } else if (!strcmp(argv[i],"-c")) {
357 358 359 360 361
            fprintf(stderr,
"Reading last argument from standard input using -c is deprecated.\n"
"When standard input is connected to a pipe or regular file, it is\n"
"automatically used as last argument.\n"
            );
362
        } else if (!strcmp(argv[i],"-v")) {
363
            printf("redis-cli shipped with Redis version %s\n", REDIS_VERSION);
364
            exit(0);
A
antirez 已提交
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
        } else {
            break;
        }
    }
    return i;
}

static sds readArgFromStdin(void) {
    char buf[1024];
    sds arg = sdsempty();

    while(1) {
        int nread = read(fileno(stdin),buf,1024);

        if (nread == 0) break;
        else if (nread == -1) {
            perror("Reading from standard input");
            exit(1);
        }
        arg = sdscatlen(arg,buf,nread);
    }
    return arg;
}

A
antirez 已提交
389
static void usage() {
390
    fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
391 392
    fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
    fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
A
antirez 已提交
393 394
    fprintf(stderr, "example: redis-cli get my_passwd\n");
    fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
395
    fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
A
antirez 已提交
396 397 398
    exit(1);
}

399 400 401
/* Turn the plain C strings into Sds strings */
static char **convertToSds(int count, char** args) {
  int j;
402
  char **sds = zmalloc(sizeof(char*)*count);
403 404 405 406 407 408 409

  for(j = 0; j < count; j++)
    sds[j] = sdsnew(args[j]);

  return sds;
}

410
#define LINE_BUFLEN 4096
411
static void repl() {
412
    int argc, j;
413 414
    char *line;
    sds *argv;
415

416
    config.interactive = 1;
A
antirez 已提交
417
    while((line = linenoise("redis> ")) != NULL) {
418
        if (line[0] != '\0') {
419
            argv = sdssplitargs(line,&argc);
420
            linenoiseHistoryAdd(line);
421
            if (config.historyfile) linenoiseHistorySave(config.historyfile);
422 423 424 425
            if (argv == NULL) {
                printf("Invalid argument(s)\n");
                continue;
            } else if (argc > 0) {
426 427
                if (strcasecmp(argv[0],"quit") == 0 ||
                    strcasecmp(argv[0],"exit") == 0)
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
                {
                    exit(0);
                } else {
                    int err;

                    if ((err = cliSendCommand(argc, argv, 1)) != 0) {
                        if (err == ECONNRESET) {
                            printf("Reconnecting... ");
                            fflush(stdout);
                            if (cliConnect(1) == -1) exit(1);
                            printf("OK\n");
                            cliSendCommand(argc,argv,1);
                        }
                    }
                }
443 444 445 446
            }
            /* Free the argument vector */
            for (j = 0; j < argc; j++)
                sdsfree(argv[j]);
447
            zfree(argv);
448
        }
449
        /* linenoise() returns malloc-ed lines like readline() */
450
        free(line);
451 452 453 454
    }
    exit(0);
}

455 456
static int noninteractive(int argc, char **argv) {
    int retval = 0;
457
    if (config.stdinarg) {
458 459 460 461 462 463 464 465 466 467
        argv = zrealloc(argv, (argc+1)*sizeof(char*));
        argv[argc] = readArgFromStdin();
        retval = cliSendCommand(argc+1, argv, config.repeat);
    } else {
        /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
        retval = cliSendCommand(argc, argv, config.repeat);
    }
    return retval;
}

A
antirez 已提交
468
int main(int argc, char **argv) {
469
    int firstarg;
A
antirez 已提交
470 471 472

    config.hostip = "127.0.0.1";
    config.hostport = 6379;
473
    config.repeat = 1;
I
ian 已提交
474
    config.dbnum = 0;
475
    config.interactive = 0;
476
    config.shutdown = 0;
477 478
    config.monitor_mode = 0;
    config.pubsub_mode = 0;
479
    config.raw_output = 0;
480
    config.stdinarg = 0;
A
antirez 已提交
481
    config.auth = NULL;
482
    config.historyfile = NULL;
483
    config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
484
    config.mb_sep = '\n';
485 486 487 488 489 490

    if (getenv("HOME") != NULL) {
        config.historyfile = malloc(256);
        snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
        linenoiseHistoryLoad(config.historyfile);
    }
A
antirez 已提交
491 492 493 494 495

    firstarg = parseOptions(argc,argv);
    argc -= firstarg;
    argv += firstarg;

496 497
    if (config.auth != NULL) {
        char *authargv[2];
498
        int dbnum = config.dbnum;
499

500 501 502 503
        /* We need to save the real configured database number and set it to
         * zero here, otherwise cliSendCommand() will try to perform the
         * SELECT command before the authentication, and it will fail. */
        config.dbnum = 0;
504 505 506
        authargv[0] = "AUTH";
        authargv[1] = config.auth;
        cliSendCommand(2, convertToSds(2, authargv), 1);
507
        config.dbnum = dbnum; /* restore the right DB number */
508 509
    }

510 511
    /* Start interactive mode when no command is provided */
    if (argc == 0) repl();
512 513
    /* Otherwise, we have some arguments to execute */
    return noninteractive(argc,convertToSds(argc,argv));
A
antirez 已提交
514
}