redis-cli.c 21.7 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>
41
#include <sys/time.h>
42
#include <assert.h>
A
antirez 已提交
43

P
Pieter Noordhuis 已提交
44
#include "hiredis.h"
A
antirez 已提交
45 46
#include "sds.h"
#include "zmalloc.h"
47
#include "linenoise.h"
48
#include "help.h"
A
antirez 已提交
49 50 51

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

P
Pieter Noordhuis 已提交
52
static redisContext *context;
A
antirez 已提交
53 54 55
static struct config {
    char *hostip;
    int hostport;
56
    char *hostsocket;
57
    long repeat;
I
ian 已提交
58
    int dbnum;
59
    int interactive;
60
    int shutdown;
61 62
    int monitor_mode;
    int pubsub_mode;
63
    int stdinarg; /* get last arg from stdin. (-x option) */
A
antirez 已提交
64
    char *auth;
65
    char *historyfile;
P
Pieter Noordhuis 已提交
66 67
    int raw_output; /* output mode per command */
    sds mb_delim;
A
antirez 已提交
68 69
} config;

A
antirez 已提交
70
static void usage();
71
char *redisGitSHA1(void);
72
char *redisGitDirty(void);
73

74 75 76 77 78 79 80 81 82 83 84 85 86 87
/*------------------------------------------------------------------------------
 * Utility functions
 *--------------------------------------------------------------------------- */

static long long mstime(void) {
    struct timeval tv;
    long long mst;

    gettimeofday(&tv, NULL);
    mst = ((long)tv.tv_sec)*1000;
    mst += tv.tv_usec/1000;
    return mst;
}

88 89 90 91
/*------------------------------------------------------------------------------
 * Help functions
 *--------------------------------------------------------------------------- */

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
#define CLI_HELP_COMMAND 1
#define CLI_HELP_GROUP 2

typedef struct {
    int type;
    int argc;
    sds *argv;
    sds full;

    /* Only used for help on commands */
    struct commandHelp *org;
} helpEntry;

static helpEntry *helpEntries;
static int helpEntriesLen;

108 109 110 111 112 113 114 115 116 117 118 119 120 121
static sds cliVersion() {
    sds version;
    version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);

    /* Add git commit and working tree status when available */
    if (strtoll(redisGitSHA1(),NULL,16)) {
        version = sdscatprintf(version, " (git:%s", redisGitSHA1());
        if (strtoll(redisGitDirty(),NULL,10))
            version = sdscatprintf(version, "-dirty");
        version = sdscat(version, ")");
    }
    return version;
}

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
static void cliInitHelp() {
    int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
    int groupslen = sizeof(commandGroups)/sizeof(char*);
    int i, len, pos = 0;
    helpEntry tmp;

    helpEntriesLen = len = commandslen+groupslen;
    helpEntries = malloc(sizeof(helpEntry)*len);

    for (i = 0; i < groupslen; i++) {
        tmp.argc = 1;
        tmp.argv = malloc(sizeof(sds));
        tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
        tmp.full = tmp.argv[0];
        tmp.type = CLI_HELP_GROUP;
        tmp.org = NULL;
        helpEntries[pos++] = tmp;
    }

    for (i = 0; i < commandslen; i++) {
        tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
        tmp.full = sdsnew(commandHelp[i].name);
        tmp.type = CLI_HELP_COMMAND;
        tmp.org = &commandHelp[i];
        helpEntries[pos++] = tmp;
    }
}

150
/* Output command help to stdout. */
151 152 153 154 155 156 157
static void cliOutputCommandHelp(struct commandHelp *help, int group) {
    printf("\r\n  \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
    printf("  \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
    printf("  \x1b[33msince:\x1b[0m %s\r\n", help->since);
    if (group) {
        printf("  \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
    }
158 159
}

160 161
/* Print generic help. */
static void cliOutputGenericHelp() {
162
    sds version = cliVersion();
163 164 165 166 167 168
    printf(
        "redis-cli %s\r\n"
        "Type: \"help @<group>\" to get a list of commands in <group>\r\n"
        "      \"help <command>\" for help on <command>\r\n"
        "      \"help <tab>\" to get a list of possible help topics\r\n"
        "      \"quit\" to exit\r\n",
169
        version
170
    );
171
    sdsfree(version);
172 173 174
}

/* Output all command help, filtering by group or command name. */
175
static void cliOutputHelp(int argc, char **argv) {
176
    int i, j, len;
177
    int group = -1;
178 179
    helpEntry *entry;
    struct commandHelp *help;
180

181 182
    if (argc == 0) {
        cliOutputGenericHelp();
183
        return;
184 185 186 187 188 189 190 191
    } else if (argc > 0 && argv[0][0] == '@') {
        len = sizeof(commandGroups)/sizeof(char*);
        for (i = 0; i < len; i++) {
            if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
                group = i;
                break;
            }
        }
192 193
    }

194
    assert(argc > 0);
195 196 197 198 199
    for (i = 0; i < helpEntriesLen; i++) {
        entry = &helpEntries[i];
        if (entry->type != CLI_HELP_COMMAND) continue;

        help = entry->org;
200
        if (group == -1) {
201 202 203 204 205 206 207 208
            /* Compare all arguments */
            if (argc == entry->argc) {
                for (j = 0; j < argc; j++) {
                    if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
                }
                if (j == argc) {
                    cliOutputCommandHelp(help,1);
                }
209 210 211
            }
        } else {
            if (group == help->group) {
212
                cliOutputCommandHelp(help,0);
213 214 215
            }
        }
    }
216 217 218 219 220 221 222 223
    printf("\r\n");
}

static void completionCallback(const char *buf, linenoiseCompletions *lc) {
    size_t startpos = 0;
    int mask;
    int i;
    size_t matchlen;
224
    sds tmp;
225 226 227 228

    if (strncasecmp(buf,"help ",5) == 0) {
        startpos = 5;
        while (isspace(buf[startpos])) startpos++;
229
        mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
230
    } else {
231
        mask = CLI_HELP_COMMAND;
232 233
    }

234 235
    for (i = 0; i < helpEntriesLen; i++) {
        if (!(helpEntries[i].type & mask)) continue;
236 237

        matchlen = strlen(buf+startpos);
238 239 240
        if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
            tmp = sdsnewlen(buf,startpos);
            tmp = sdscat(tmp,helpEntries[i].full);
241
            linenoiseAddCompletion(lc,tmp);
242
            sdsfree(tmp);
243 244
        }
    }
245 246
}

247 248 249 250
/*------------------------------------------------------------------------------
 * Networking / parsing
 *--------------------------------------------------------------------------- */

P
Pieter Noordhuis 已提交
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
/* Send AUTH command to the server */
static int cliAuth() {
    redisReply *reply;
    if (config.auth == NULL) return REDIS_OK;

    reply = redisCommand(context,"AUTH %s",config.auth);
    if (reply != NULL) {
        freeReplyObject(reply);
        return REDIS_OK;
    }
    return REDIS_ERR;
}

/* Send SELECT dbnum to the server */
static int cliSelect() {
    redisReply *reply;
    char dbnum[16];
    if (config.dbnum == 0) return REDIS_OK;

    snprintf(dbnum,sizeof(dbnum),"%d",config.dbnum);
    reply = redisCommand(context,"SELECT %s",dbnum);
    if (reply != NULL) {
        freeReplyObject(reply);
        return REDIS_OK;
    }
    return REDIS_ERR;
}

279 280 281
/* 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) {
P
Pieter Noordhuis 已提交
282 283 284
    if (context == NULL || force) {
        if (context != NULL)
            redisFree(context);
A
antirez 已提交
285

286
        if (config.hostsocket == NULL) {
P
Pieter Noordhuis 已提交
287
            context = redisConnect(config.hostip,config.hostport);
288
        } else {
P
Pieter Noordhuis 已提交
289
            context = redisConnectUnix(config.hostsocket);
290
        }
P
Pieter Noordhuis 已提交
291 292

        if (context->err) {
293 294
            fprintf(stderr,"Could not connect to Redis at ");
            if (config.hostsocket == NULL)
P
Pieter Noordhuis 已提交
295
                fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
296
            else
P
Pieter Noordhuis 已提交
297 298 299 300
                fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
            redisFree(context);
            context = NULL;
            return REDIS_ERR;
301
        }
A
antirez 已提交
302

P
Pieter Noordhuis 已提交
303 304 305 306 307
        /* Do AUTH and select the right DB. */
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
A
antirez 已提交
308
    }
P
Pieter Noordhuis 已提交
309
    return REDIS_OK;
A
antirez 已提交
310 311
}

P
Pieter Noordhuis 已提交
312 313 314 315
static void cliPrintContextErrorAndExit() {
    if (context == NULL) return;
    fprintf(stderr,"Error: %s\n",context->errstr);
    exit(1);
A
antirez 已提交
316 317
}

P
Pieter Noordhuis 已提交
318
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
P
Pieter Noordhuis 已提交
319 320 321
    sds out = sdsempty();
    switch (r->type) {
    case REDIS_REPLY_ERROR:
P
Pieter Noordhuis 已提交
322
        out = sdscatprintf(out,"(error) %s\n", r->str);
P
Pieter Noordhuis 已提交
323 324 325 326 327 328
    break;
    case REDIS_REPLY_STATUS:
        out = sdscat(out,r->str);
        out = sdscat(out,"\n");
    break;
    case REDIS_REPLY_INTEGER:
P
Pieter Noordhuis 已提交
329
        out = sdscatprintf(out,"(integer) %lld\n",r->integer);
P
Pieter Noordhuis 已提交
330 331
    break;
    case REDIS_REPLY_STRING:
P
Pieter Noordhuis 已提交
332 333 334 335
        /* If you are producing output for the standard output we want
        * a more interesting output with quoted characters and so forth */
        out = sdscatrepr(out,r->str,r->len);
        out = sdscat(out,"\n");
P
Pieter Noordhuis 已提交
336 337 338 339 340 341 342
    break;
    case REDIS_REPLY_NIL:
        out = sdscat(out,"(nil)\n");
    break;
    case REDIS_REPLY_ARRAY:
        if (r->elements == 0) {
            out = sdscat(out,"(empty list or set)\n");
343
        } else {
344 345 346 347
            unsigned int i, idxlen = 0;
            char _prefixlen[16];
            char _prefixfmt[16];
            sds _prefix;
P
Pieter Noordhuis 已提交
348 349
            sds tmp;

350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
            /* Calculate chars needed to represent the largest index */
            i = r->elements;
            do {
                idxlen++;
                i /= 10;
            } while(i);

            /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
            memset(_prefixlen,' ',idxlen+2);
            _prefixlen[idxlen+2] = '\0';
            _prefix = sdscat(sdsnew(prefix),_prefixlen);

            /* Setup prefix format for every entry */
            snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);

P
Pieter Noordhuis 已提交
365
            for (i = 0; i < r->elements; i++) {
366 367 368 369 370
                /* Don't use the prefix for the first element, as the parent
                 * caller already prepended the index number. */
                out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);

                /* Format the multi bulk entry */
P
Pieter Noordhuis 已提交
371
                tmp = cliFormatReplyTTY(r->element[i],_prefix);
P
Pieter Noordhuis 已提交
372 373 374
                out = sdscatlen(out,tmp,sdslen(tmp));
                sdsfree(tmp);
            }
375
            sdsfree(_prefix);
376
        }
P
Pieter Noordhuis 已提交
377
    break;
378
    default:
P
Pieter Noordhuis 已提交
379 380
        fprintf(stderr,"Unknown reply type: %d\n", r->type);
        exit(1);
381
    }
P
Pieter Noordhuis 已提交
382
    return out;
383 384
}

P
Pieter Noordhuis 已提交
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
static sds cliFormatReplyRaw(redisReply *r) {
    sds out = sdsempty(), tmp;
    size_t i;

    switch (r->type) {
    case REDIS_REPLY_NIL:
        /* Nothing... */
    break;
    case REDIS_REPLY_ERROR:
    case REDIS_REPLY_STATUS:
    case REDIS_REPLY_STRING:
        out = sdscatlen(out,r->str,r->len);
    break;
    case REDIS_REPLY_INTEGER:
        out = sdscatprintf(out,"%lld",r->integer);
    break;
    case REDIS_REPLY_ARRAY:
        for (i = 0; i < r->elements; i++) {
            if (i > 0) out = sdscat(out,config.mb_delim);
            tmp = cliFormatReplyRaw(r->element[i]);
            out = sdscatlen(out,tmp,sdslen(tmp));
            sdsfree(tmp);
        }
    break;
    default:
        fprintf(stderr,"Unknown reply type: %d\n", r->type);
        exit(1);
    }
    return out;
}

static int cliReadReply(int output_raw_strings) {
417
    void *_reply;
P
Pieter Noordhuis 已提交
418 419 420
    redisReply *reply;
    sds out;

421
    if (redisGetReply(context,&_reply) != REDIS_OK) {
P
Pieter Noordhuis 已提交
422 423 424 425 426 427 428 429 430 431 432
        if (config.shutdown)
            return REDIS_OK;
        if (config.interactive) {
            /* Filter cases where we should reconnect */
            if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
                return REDIS_ERR;
            if (context->err == REDIS_ERR_EOF)
                return REDIS_ERR;
        }
        cliPrintContextErrorAndExit();
        return REDIS_ERR; /* avoid compiler warning */
I
ian 已提交
433
    }
P
Pieter Noordhuis 已提交
434

435
    reply = (redisReply*)_reply;
P
Pieter Noordhuis 已提交
436 437 438 439 440 441 442 443 444 445
    if (output_raw_strings) {
        out = cliFormatReplyRaw(reply);
    } else {
        if (config.raw_output) {
            out = cliFormatReplyRaw(reply);
            out = sdscat(out,"\n");
        } else {
            out = cliFormatReplyTTY(reply,"");
        }
    }
P
Pieter Noordhuis 已提交
446 447
    fwrite(out,sdslen(out),1,stdout);
    sdsfree(out);
P
Pieter Noordhuis 已提交
448
    freeReplyObject(reply);
P
Pieter Noordhuis 已提交
449
    return REDIS_OK;
I
ian 已提交
450 451
}

452
static int cliSendCommand(int argc, char **argv, int repeat) {
453
    char *command = argv[0];
P
Pieter Noordhuis 已提交
454
    size_t *argvlen;
P
Pieter Noordhuis 已提交
455
    int j, output_raw;
A
antirez 已提交
456

A
antirez 已提交
457 458 459 460 461
    if (context == NULL) {
        printf("Not connected, please use: connect <host> <port>\n");
        return REDIS_OK;
    }

P
Pieter Noordhuis 已提交
462
    output_raw = !strcasecmp(command,"info");
463 464
    if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
        cliOutputHelp(--argc, ++argv);
P
Pieter Noordhuis 已提交
465
        return REDIS_OK;
466
    }
467 468 469 470
    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;
A
antirez 已提交
471

P
Pieter Noordhuis 已提交
472 473 474 475
    /* Setup argument length */
    argvlen = malloc(argc*sizeof(size_t));
    for (j = 0; j < argc; j++)
        argvlen[j] = sdslen(argv[j]);
476

477
    while(repeat--) {
P
Pieter Noordhuis 已提交
478
        redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
479
        while (config.monitor_mode) {
P
Pieter Noordhuis 已提交
480
            if (cliReadReply(output_raw) != REDIS_OK) exit(1);
481
            fflush(stdout);
482 483
        }

484
        if (config.pubsub_mode) {
P
Pieter Noordhuis 已提交
485 486
            if (!config.raw_output)
                printf("Reading messages... (press Ctrl-C to quit)\n");
487
            while (1) {
P
Pieter Noordhuis 已提交
488
                if (cliReadReply(output_raw) != REDIS_OK) exit(1);
489 490 491
            }
        }

P
Pieter Noordhuis 已提交
492
        if (cliReadReply(output_raw) != REDIS_OK)
P
Pieter Noordhuis 已提交
493
            return REDIS_ERR;
A
antirez 已提交
494
    }
P
Pieter Noordhuis 已提交
495
    return REDIS_OK;
A
antirez 已提交
496 497
}

498 499 500 501
/*------------------------------------------------------------------------------
 * User interface
 *--------------------------------------------------------------------------- */

A
antirez 已提交
502 503 504 505 506
static int parseOptions(int argc, char **argv) {
    int i;

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

A
antirez 已提交
508
        if (!strcmp(argv[i],"-h") && !lastarg) {
A
antirez 已提交
509 510
            sdsfree(config.hostip);
            config.hostip = sdsnew(argv[i+1]);
A
antirez 已提交
511
            i++;
A
antirez 已提交
512 513
        } else if (!strcmp(argv[i],"-h") && lastarg) {
            usage();
514 515
        } else if (!strcmp(argv[i],"--help")) {
            usage();
516 517
        } else if (!strcmp(argv[i],"-x")) {
            config.stdinarg = 1;
A
antirez 已提交
518 519 520
        } else if (!strcmp(argv[i],"-p") && !lastarg) {
            config.hostport = atoi(argv[i+1]);
            i++;
521 522 523
        } else if (!strcmp(argv[i],"-s") && !lastarg) {
            config.hostsocket = argv[i+1];
            i++;
524 525 526
        } else if (!strcmp(argv[i],"-r") && !lastarg) {
            config.repeat = strtoll(argv[i+1],NULL,10);
            i++;
I
ian 已提交
527 528 529
        } else if (!strcmp(argv[i],"-n") && !lastarg) {
            config.dbnum = atoi(argv[i+1]);
            i++;
530
        } else if (!strcmp(argv[i],"-a") && !lastarg) {
A
antirez 已提交
531
            config.auth = argv[i+1];
532
            i++;
P
Pieter Noordhuis 已提交
533 534
        } else if (!strcmp(argv[i],"--raw")) {
            config.raw_output = 1;
535 536 537 538
        } else if (!strcmp(argv[i],"-d") && !lastarg) {
            sdsfree(config.mb_delim);
            config.mb_delim = sdsnew(argv[i+1]);
            i++;
539 540 541 542
        } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
            sds version = cliVersion();
            printf("redis-cli %s\n", version);
            sdsfree(version);
543
            exit(0);
A
antirez 已提交
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
        } 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 已提交
568
static void usage() {
569 570 571 572 573 574 575 576 577 578 579 580
    sds version = cliVersion();
    fprintf(stderr,
"redis-cli %s\n"
"\n"
"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
"  -h <hostname>    Server hostname (default: 127.0.0.1)\n"
"  -p <port>        Server port (default: 6379)\n"
"  -s <socket>      Server socket (overrides hostname and port)\n"
"  -a <password>    Password to use when connecting to the server\n"
"  -r <repeat>      Execute specified command N times\n"
"  -n <db>          Database number\n"
"  -x               Read last argument from STDIN\n"
581
"  -d <delimiter>   Multi-bulk delimiter in for raw formatting (default: \\n)\n"
P
Pieter Noordhuis 已提交
582
"  --raw            Use raw formatting for replies (default when STDOUT is not a tty)\n"
583 584 585 586 587 588 589 590 591 592 593 594 595
"  --help           Output this help and exit\n"
"  --version        Output version and exit\n"
"\n"
"Examples:\n"
"  cat /etc/passwd | redis-cli -x set mypasswd\n"
"  redis-cli get mypasswd\n"
"  redis-cli -r 100 lpush mylist x\n"
"\n"
"When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands.\n"
"\n",
        version);
    sdsfree(version);
A
antirez 已提交
596 597 598
    exit(1);
}

599 600 601
/* Turn the plain C strings into Sds strings */
static char **convertToSds(int count, char** args) {
  int j;
602
  char **sds = zmalloc(sizeof(char*)*count);
603 604 605 606 607 608 609

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

  return sds;
}

610
#define LINE_BUFLEN 4096
611
static void repl() {
612
    int argc, j;
613 614
    char *line;
    sds *argv;
615

616
    config.interactive = 1;
617
    linenoiseSetCompletionCallback(completionCallback);
618

619
    while((line = linenoise(context ? "redis> " : "not connected> ")) != NULL) {
620
        if (line[0] != '\0') {
621
            argv = sdssplitargs(line,&argc);
622
            linenoiseHistoryAdd(line);
623
            if (config.historyfile) linenoiseHistorySave(config.historyfile);
624 625 626 627
            if (argv == NULL) {
                printf("Invalid argument(s)\n");
                continue;
            } else if (argc > 0) {
628 629
                if (strcasecmp(argv[0],"quit") == 0 ||
                    strcasecmp(argv[0],"exit") == 0)
630 631
                {
                    exit(0);
A
antirez 已提交
632 633 634 635 636
                } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
                    sdsfree(config.hostip);
                    config.hostip = sdsnew(argv[1]);
                    config.hostport = atoi(argv[2]);
                    cliConnect(1);
637 638
                } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
                    linenoiseClearScreen();
639
                } else {
640
                    long long start_time = mstime(), elapsed;
641

P
Pieter Noordhuis 已提交
642
                    if (cliSendCommand(argc,argv,1) != REDIS_OK) {
A
antirez 已提交
643
                        cliConnect(1);
P
Pieter Noordhuis 已提交
644 645 646 647 648

                        /* If we still cannot send the command,
                         * print error and abort. */
                        if (cliSendCommand(argc,argv,1) != REDIS_OK)
                            cliPrintContextErrorAndExit();
649
                    }
650
                    elapsed = mstime()-start_time;
P
Pieter Noordhuis 已提交
651 652 653
                    if (elapsed >= 500) {
                        printf("(%.2fs)\n",(double)elapsed/1000);
                    }
654
                }
655 656 657 658
            }
            /* Free the argument vector */
            for (j = 0; j < argc; j++)
                sdsfree(argv[j]);
659
            zfree(argv);
660
        }
661
        /* linenoise() returns malloc-ed lines like readline() */
662
        free(line);
663 664 665 666
    }
    exit(0);
}

667 668
static int noninteractive(int argc, char **argv) {
    int retval = 0;
669
    if (config.stdinarg) {
670 671 672 673 674 675 676 677 678 679
        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 已提交
680
int main(int argc, char **argv) {
681
    int firstarg;
A
antirez 已提交
682

A
antirez 已提交
683
    config.hostip = sdsnew("127.0.0.1");
A
antirez 已提交
684
    config.hostport = 6379;
685
    config.hostsocket = NULL;
686
    config.repeat = 1;
I
ian 已提交
687
    config.dbnum = 0;
688
    config.interactive = 0;
689
    config.shutdown = 0;
690 691
    config.monitor_mode = 0;
    config.pubsub_mode = 0;
692
    config.stdinarg = 0;
A
antirez 已提交
693
    config.auth = NULL;
694
    config.historyfile = NULL;
P
Pieter Noordhuis 已提交
695 696
    config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL);
    config.mb_delim = sdsnew("\n");
697
    cliInitHelp();
698 699 700 701 702 703

    if (getenv("HOME") != NULL) {
        config.historyfile = malloc(256);
        snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
        linenoiseHistoryLoad(config.historyfile);
    }
A
antirez 已提交
704 705 706 707 708

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

P
Pieter Noordhuis 已提交
709 710
    /* Try to connect */
    if (cliConnect(0) != REDIS_OK) exit(1);
711

712 713
    /* Start interactive mode when no command is provided */
    if (argc == 0) repl();
714 715
    /* Otherwise, we have some arguments to execute */
    return noninteractive(argc,convertToSds(argc,argv));
A
antirez 已提交
716
}