redis-cli.c 22.2 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;
68
    char prompt[32];
A
antirez 已提交
69 70
} config;

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

75 76 77 78 79 80 81 82 83 84 85 86 87 88
/*------------------------------------------------------------------------------
 * 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;
}

89 90 91 92 93 94 95
static void cliRefreshPrompt(void) {
    if (config.dbnum == 0)
        snprintf(config.prompt,sizeof(config.prompt),"redis> ");
    else
        snprintf(config.prompt,sizeof(config.prompt),"redis:%d> ",config.dbnum);
}

96 97 98 99
/*------------------------------------------------------------------------------
 * Help functions
 *--------------------------------------------------------------------------- */

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
#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;

116 117 118 119 120 121 122 123 124 125 126 127 128 129
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;
}

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
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;
    }
}

158
/* Output command help to stdout. */
159 160 161 162 163 164 165
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]);
    }
166 167
}

168 169
/* Print generic help. */
static void cliOutputGenericHelp() {
170
    sds version = cliVersion();
171 172 173 174 175 176
    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",
177
        version
178
    );
179
    sdsfree(version);
180 181 182
}

/* Output all command help, filtering by group or command name. */
183
static void cliOutputHelp(int argc, char **argv) {
184
    int i, j, len;
185
    int group = -1;
186 187
    helpEntry *entry;
    struct commandHelp *help;
188

189 190
    if (argc == 0) {
        cliOutputGenericHelp();
191
        return;
192 193 194 195 196 197 198 199
    } 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;
            }
        }
200 201
    }

202
    assert(argc > 0);
203 204 205 206 207
    for (i = 0; i < helpEntriesLen; i++) {
        entry = &helpEntries[i];
        if (entry->type != CLI_HELP_COMMAND) continue;

        help = entry->org;
208
        if (group == -1) {
209 210 211 212 213 214 215 216
            /* 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);
                }
217 218 219
            }
        } else {
            if (group == help->group) {
220
                cliOutputCommandHelp(help,0);
221 222 223
            }
        }
    }
224 225 226 227 228 229 230 231
    printf("\r\n");
}

static void completionCallback(const char *buf, linenoiseCompletions *lc) {
    size_t startpos = 0;
    int mask;
    int i;
    size_t matchlen;
232
    sds tmp;
233 234 235 236

    if (strncasecmp(buf,"help ",5) == 0) {
        startpos = 5;
        while (isspace(buf[startpos])) startpos++;
237
        mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
238
    } else {
239
        mask = CLI_HELP_COMMAND;
240 241
    }

242 243
    for (i = 0; i < helpEntriesLen; i++) {
        if (!(helpEntries[i].type & mask)) continue;
244 245

        matchlen = strlen(buf+startpos);
246 247 248
        if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
            tmp = sdsnewlen(buf,startpos);
            tmp = sdscat(tmp,helpEntries[i].full);
249
            linenoiseAddCompletion(lc,tmp);
250
            sdsfree(tmp);
251 252
        }
    }
253 254
}

255 256 257 258
/*------------------------------------------------------------------------------
 * Networking / parsing
 *--------------------------------------------------------------------------- */

P
Pieter Noordhuis 已提交
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
/* 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;
    if (config.dbnum == 0) return REDIS_OK;

277
    reply = redisCommand(context,"SELECT %d",config.dbnum);
P
Pieter Noordhuis 已提交
278 279 280 281 282 283 284
    if (reply != NULL) {
        freeReplyObject(reply);
        return REDIS_OK;
    }
    return REDIS_ERR;
}

285 286 287
/* 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 已提交
288 289 290
    if (context == NULL || force) {
        if (context != NULL)
            redisFree(context);
A
antirez 已提交
291

292
        if (config.hostsocket == NULL) {
P
Pieter Noordhuis 已提交
293
            context = redisConnect(config.hostip,config.hostport);
294
        } else {
P
Pieter Noordhuis 已提交
295
            context = redisConnectUnix(config.hostsocket);
296
        }
P
Pieter Noordhuis 已提交
297 298

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

P
Pieter Noordhuis 已提交
309 310 311 312 313
        /* Do AUTH and select the right DB. */
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
A
antirez 已提交
314
    }
P
Pieter Noordhuis 已提交
315
    return REDIS_OK;
A
antirez 已提交
316 317
}

P
Pieter Noordhuis 已提交
318 319 320 321
static void cliPrintContextErrorAndExit() {
    if (context == NULL) return;
    fprintf(stderr,"Error: %s\n",context->errstr);
    exit(1);
A
antirez 已提交
322 323
}

P
Pieter Noordhuis 已提交
324
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
P
Pieter Noordhuis 已提交
325 326 327
    sds out = sdsempty();
    switch (r->type) {
    case REDIS_REPLY_ERROR:
P
Pieter Noordhuis 已提交
328
        out = sdscatprintf(out,"(error) %s\n", r->str);
P
Pieter Noordhuis 已提交
329 330 331 332 333 334
    break;
    case REDIS_REPLY_STATUS:
        out = sdscat(out,r->str);
        out = sdscat(out,"\n");
    break;
    case REDIS_REPLY_INTEGER:
P
Pieter Noordhuis 已提交
335
        out = sdscatprintf(out,"(integer) %lld\n",r->integer);
P
Pieter Noordhuis 已提交
336 337
    break;
    case REDIS_REPLY_STRING:
P
Pieter Noordhuis 已提交
338 339 340 341
        /* 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 已提交
342 343 344 345 346 347 348
    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");
349
        } else {
350 351 352 353
            unsigned int i, idxlen = 0;
            char _prefixlen[16];
            char _prefixfmt[16];
            sds _prefix;
P
Pieter Noordhuis 已提交
354 355
            sds tmp;

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
            /* 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 已提交
371
            for (i = 0; i < r->elements; i++) {
372 373 374 375 376
                /* 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 已提交
377
                tmp = cliFormatReplyTTY(r->element[i],_prefix);
P
Pieter Noordhuis 已提交
378 379 380
                out = sdscatlen(out,tmp,sdslen(tmp));
                sdsfree(tmp);
            }
381
            sdsfree(_prefix);
382
        }
P
Pieter Noordhuis 已提交
383
    break;
384
    default:
P
Pieter Noordhuis 已提交
385 386
        fprintf(stderr,"Unknown reply type: %d\n", r->type);
        exit(1);
387
    }
P
Pieter Noordhuis 已提交
388
    return out;
389 390
}

P
Pieter Noordhuis 已提交
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 417 418 419 420 421 422
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) {
423
    void *_reply;
P
Pieter Noordhuis 已提交
424 425 426
    redisReply *reply;
    sds out;

427
    if (redisGetReply(context,&_reply) != REDIS_OK) {
P
Pieter Noordhuis 已提交
428 429 430 431 432 433 434 435 436 437 438
        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 已提交
439
    }
P
Pieter Noordhuis 已提交
440

441
    reply = (redisReply*)_reply;
P
Pieter Noordhuis 已提交
442 443 444 445 446 447 448 449 450 451
    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 已提交
452 453
    fwrite(out,sdslen(out),1,stdout);
    sdsfree(out);
P
Pieter Noordhuis 已提交
454
    freeReplyObject(reply);
P
Pieter Noordhuis 已提交
455
    return REDIS_OK;
I
ian 已提交
456 457
}

458
static int cliSendCommand(int argc, char **argv, int repeat) {
459
    char *command = argv[0];
P
Pieter Noordhuis 已提交
460
    size_t *argvlen;
P
Pieter Noordhuis 已提交
461
    int j, output_raw;
A
antirez 已提交
462

A
antirez 已提交
463 464 465 466 467
    if (context == NULL) {
        printf("Not connected, please use: connect <host> <port>\n");
        return REDIS_OK;
    }

P
Pieter Noordhuis 已提交
468
    output_raw = !strcasecmp(command,"info");
469 470
    if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
        cliOutputHelp(--argc, ++argv);
P
Pieter Noordhuis 已提交
471
        return REDIS_OK;
472
    }
473 474 475 476
    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 已提交
477

P
Pieter Noordhuis 已提交
478 479 480 481
    /* Setup argument length */
    argvlen = malloc(argc*sizeof(size_t));
    for (j = 0; j < argc; j++)
        argvlen[j] = sdslen(argv[j]);
482

483
    while(repeat--) {
P
Pieter Noordhuis 已提交
484
        redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
485
        while (config.monitor_mode) {
P
Pieter Noordhuis 已提交
486
            if (cliReadReply(output_raw) != REDIS_OK) exit(1);
487
            fflush(stdout);
488 489
        }

490
        if (config.pubsub_mode) {
P
Pieter Noordhuis 已提交
491 492
            if (!config.raw_output)
                printf("Reading messages... (press Ctrl-C to quit)\n");
493
            while (1) {
P
Pieter Noordhuis 已提交
494
                if (cliReadReply(output_raw) != REDIS_OK) exit(1);
495 496 497
            }
        }

498 499
        if (cliReadReply(output_raw) != REDIS_OK) {
            free(argvlen);
P
Pieter Noordhuis 已提交
500
            return REDIS_ERR;
501 502
        } else {
            /* Store database number when SELECT was successfully executed. */
503
            if (!strcasecmp(command,"select") && argc == 2) {
504
                config.dbnum = atoi(argv[1]);
505 506
                cliRefreshPrompt();
            }
507
        }
A
antirez 已提交
508
    }
509 510

    free(argvlen);
P
Pieter Noordhuis 已提交
511
    return REDIS_OK;
A
antirez 已提交
512 513
}

514 515 516 517
/*------------------------------------------------------------------------------
 * User interface
 *--------------------------------------------------------------------------- */

A
antirez 已提交
518 519 520 521 522
static int parseOptions(int argc, char **argv) {
    int i;

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

A
antirez 已提交
524
        if (!strcmp(argv[i],"-h") && !lastarg) {
A
antirez 已提交
525 526
            sdsfree(config.hostip);
            config.hostip = sdsnew(argv[i+1]);
A
antirez 已提交
527
            i++;
A
antirez 已提交
528 529
        } else if (!strcmp(argv[i],"-h") && lastarg) {
            usage();
530 531
        } else if (!strcmp(argv[i],"--help")) {
            usage();
532 533
        } else if (!strcmp(argv[i],"-x")) {
            config.stdinarg = 1;
A
antirez 已提交
534 535 536
        } else if (!strcmp(argv[i],"-p") && !lastarg) {
            config.hostport = atoi(argv[i+1]);
            i++;
537 538 539
        } else if (!strcmp(argv[i],"-s") && !lastarg) {
            config.hostsocket = argv[i+1];
            i++;
540 541 542
        } else if (!strcmp(argv[i],"-r") && !lastarg) {
            config.repeat = strtoll(argv[i+1],NULL,10);
            i++;
I
ian 已提交
543 544 545
        } else if (!strcmp(argv[i],"-n") && !lastarg) {
            config.dbnum = atoi(argv[i+1]);
            i++;
546
        } else if (!strcmp(argv[i],"-a") && !lastarg) {
A
antirez 已提交
547
            config.auth = argv[i+1];
548
            i++;
P
Pieter Noordhuis 已提交
549 550
        } else if (!strcmp(argv[i],"--raw")) {
            config.raw_output = 1;
551 552 553 554
        } else if (!strcmp(argv[i],"-d") && !lastarg) {
            sdsfree(config.mb_delim);
            config.mb_delim = sdsnew(argv[i+1]);
            i++;
555 556 557 558
        } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
            sds version = cliVersion();
            printf("redis-cli %s\n", version);
            sdsfree(version);
559
            exit(0);
A
antirez 已提交
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
        } 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 已提交
584
static void usage() {
585 586 587 588 589 590 591 592 593 594 595 596
    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"
597
"  -d <delimiter>   Multi-bulk delimiter in for raw formatting (default: \\n)\n"
P
Pieter Noordhuis 已提交
598
"  --raw            Use raw formatting for replies (default when STDOUT is not a tty)\n"
599 600 601 602 603 604 605 606 607 608 609 610 611
"  --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 已提交
612 613 614
    exit(1);
}

615 616 617
/* Turn the plain C strings into Sds strings */
static char **convertToSds(int count, char** args) {
  int j;
618
  char **sds = zmalloc(sizeof(char*)*count);
619 620 621 622 623 624 625

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

  return sds;
}

626
#define LINE_BUFLEN 4096
627
static void repl() {
628
    int argc, j;
629 630
    char *line;
    sds *argv;
631

632
    config.interactive = 1;
633
    linenoiseSetCompletionCallback(completionCallback);
634

635 636
    cliRefreshPrompt();
    while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
637
        if (line[0] != '\0') {
638
            argv = sdssplitargs(line,&argc);
639
            linenoiseHistoryAdd(line);
640
            if (config.historyfile) linenoiseHistorySave(config.historyfile);
641 642 643 644
            if (argv == NULL) {
                printf("Invalid argument(s)\n");
                continue;
            } else if (argc > 0) {
645 646
                if (strcasecmp(argv[0],"quit") == 0 ||
                    strcasecmp(argv[0],"exit") == 0)
647 648
                {
                    exit(0);
A
antirez 已提交
649 650 651 652 653
                } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
                    sdsfree(config.hostip);
                    config.hostip = sdsnew(argv[1]);
                    config.hostport = atoi(argv[2]);
                    cliConnect(1);
654 655
                } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
                    linenoiseClearScreen();
656
                } else {
657
                    long long start_time = mstime(), elapsed;
658

P
Pieter Noordhuis 已提交
659
                    if (cliSendCommand(argc,argv,1) != REDIS_OK) {
A
antirez 已提交
660
                        cliConnect(1);
P
Pieter Noordhuis 已提交
661 662 663 664 665

                        /* If we still cannot send the command,
                         * print error and abort. */
                        if (cliSendCommand(argc,argv,1) != REDIS_OK)
                            cliPrintContextErrorAndExit();
666
                    }
667
                    elapsed = mstime()-start_time;
P
Pieter Noordhuis 已提交
668 669 670
                    if (elapsed >= 500) {
                        printf("(%.2fs)\n",(double)elapsed/1000);
                    }
671
                }
672 673 674 675
            }
            /* Free the argument vector */
            for (j = 0; j < argc; j++)
                sdsfree(argv[j]);
676
            zfree(argv);
677
        }
678
        /* linenoise() returns malloc-ed lines like readline() */
679
        free(line);
680 681 682 683
    }
    exit(0);
}

684 685
static int noninteractive(int argc, char **argv) {
    int retval = 0;
686
    if (config.stdinarg) {
687 688 689 690 691 692 693 694 695 696
        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 已提交
697
int main(int argc, char **argv) {
698
    int firstarg;
A
antirez 已提交
699

A
antirez 已提交
700
    config.hostip = sdsnew("127.0.0.1");
A
antirez 已提交
701
    config.hostport = 6379;
702
    config.hostsocket = NULL;
703
    config.repeat = 1;
I
ian 已提交
704
    config.dbnum = 0;
705
    config.interactive = 0;
706
    config.shutdown = 0;
707 708
    config.monitor_mode = 0;
    config.pubsub_mode = 0;
709
    config.stdinarg = 0;
A
antirez 已提交
710
    config.auth = NULL;
711
    config.historyfile = NULL;
P
Pieter Noordhuis 已提交
712 713
    config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL);
    config.mb_delim = sdsnew("\n");
714
    cliInitHelp();
715 716 717 718 719 720

    if (getenv("HOME") != NULL) {
        config.historyfile = malloc(256);
        snprintf(config.historyfile,256,"%s/.rediscli_history",getenv("HOME"));
        linenoiseHistoryLoad(config.historyfile);
    }
A
antirez 已提交
721 722 723 724 725

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

P
Pieter Noordhuis 已提交
726 727
    /* Try to connect */
    if (cliConnect(0) != REDIS_OK) exit(1);
728

729 730
    /* Start interactive mode when no command is provided */
    if (argc == 0) repl();
731 732
    /* Otherwise, we have some arguments to execute */
    return noninteractive(argc,convertToSds(argc,argv));
A
antirez 已提交
733
}