]>
git.saurik.com Git - redis.git/blob - redis-cli.c
863df9bf857f81d19d118ad26999b276d7b7ace8
   1 /* Redis CLI (command line interface) 
   3  * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> 
   6  * Redistribution and use in source and binary forms, with or without 
   7  * modification, are permitted provided that the following conditions are met: 
   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. 
  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. 
  43 #include "linenoise.h" 
  45 #define REDIS_CMD_INLINE 1 
  46 #define REDIS_CMD_BULK 2 
  47 #define REDIS_CMD_MULTIBULK 4 
  49 #define REDIS_NOTUSED(V) ((void) V) 
  51 static struct config 
{ 
  69 #define CMDFLAG_NONE 0 
  70 #define CMDFLAG_RAWOUTPUT 1 
  72 static struct redisCommand cmdTable
[] = { 
  73     {"auth",2,CMDFLAG_NONE
}, 
  74     {"get",2,CMDFLAG_NONE
}, 
  75     {"set",3,CMDFLAG_NONE
}, 
  76     {"setnx",3,CMDFLAG_NONE
}, 
  77     {"setex",4,CMDFLAG_NONE
}, 
  78     {"append",3,CMDFLAG_NONE
}, 
  79     {"substr",4,CMDFLAG_NONE
}, 
  80     {"del",-2,CMDFLAG_NONE
}, 
  81     {"exists",2,CMDFLAG_NONE
}, 
  82     {"incr",2,CMDFLAG_NONE
}, 
  83     {"decr",2,CMDFLAG_NONE
}, 
  84     {"rpush",3,CMDFLAG_NONE
}, 
  85     {"lpush",3,CMDFLAG_NONE
}, 
  86     {"rpop",2,CMDFLAG_NONE
}, 
  87     {"lpop",2,CMDFLAG_NONE
}, 
  88     {"brpop",-3,CMDFLAG_NONE
}, 
  89     {"blpop",-3,CMDFLAG_NONE
}, 
  90     {"llen",2,CMDFLAG_NONE
}, 
  91     {"lindex",3,CMDFLAG_NONE
}, 
  92     {"lset",4,CMDFLAG_NONE
}, 
  93     {"lrange",4,CMDFLAG_NONE
}, 
  94     {"ltrim",4,CMDFLAG_NONE
}, 
  95     {"lrem",4,CMDFLAG_NONE
}, 
  96     {"rpoplpush",3,CMDFLAG_NONE
}, 
  97     {"sadd",3,CMDFLAG_NONE
}, 
  98     {"srem",3,CMDFLAG_NONE
}, 
  99     {"smove",4,CMDFLAG_NONE
}, 
 100     {"sismember",3,CMDFLAG_NONE
}, 
 101     {"scard",2,CMDFLAG_NONE
}, 
 102     {"spop",2,CMDFLAG_NONE
}, 
 103     {"srandmember",2,CMDFLAG_NONE
}, 
 104     {"sinter",-2,CMDFLAG_NONE
}, 
 105     {"sinterstore",-3,CMDFLAG_NONE
}, 
 106     {"sunion",-2,CMDFLAG_NONE
}, 
 107     {"sunionstore",-3,CMDFLAG_NONE
}, 
 108     {"sdiff",-2,CMDFLAG_NONE
}, 
 109     {"sdiffstore",-3,CMDFLAG_NONE
}, 
 110     {"smembers",2,CMDFLAG_NONE
}, 
 111     {"zadd",4,CMDFLAG_NONE
}, 
 112     {"zincrby",4,CMDFLAG_NONE
}, 
 113     {"zrem",3,CMDFLAG_NONE
}, 
 114     {"zremrangebyscore",4,CMDFLAG_NONE
}, 
 115     {"zunion",-4,CMDFLAG_NONE
}, 
 116     {"zinter",-4,CMDFLAG_NONE
}, 
 117     {"zrange",-4,CMDFLAG_NONE
}, 
 118     {"zrank",3,CMDFLAG_NONE
}, 
 119     {"zrevrank",3,CMDFLAG_NONE
}, 
 120     {"zrangebyscore",-4,CMDFLAG_NONE
}, 
 121     {"zcount",4,CMDFLAG_NONE
}, 
 122     {"zrevrange",-4,CMDFLAG_NONE
}, 
 123     {"zcard",2,CMDFLAG_NONE
}, 
 124     {"zscore",3,CMDFLAG_NONE
}, 
 125     {"incrby",3,CMDFLAG_NONE
}, 
 126     {"decrby",3,CMDFLAG_NONE
}, 
 127     {"getset",3,CMDFLAG_NONE
}, 
 128     {"randomkey",1,CMDFLAG_NONE
}, 
 129     {"select",2,CMDFLAG_NONE
}, 
 130     {"move",3,CMDFLAG_NONE
}, 
 131     {"rename",3,CMDFLAG_NONE
}, 
 132     {"renamenx",3,CMDFLAG_NONE
}, 
 133     {"keys",2,CMDFLAG_NONE
}, 
 134     {"dbsize",1,CMDFLAG_NONE
}, 
 135     {"ping",1,CMDFLAG_NONE
}, 
 136     {"echo",2,CMDFLAG_NONE
}, 
 137     {"save",1,CMDFLAG_NONE
}, 
 138     {"bgsave",1,CMDFLAG_NONE
}, 
 139     {"rewriteaof",1,CMDFLAG_NONE
}, 
 140     {"bgrewriteaof",1,CMDFLAG_NONE
}, 
 141     {"shutdown",1,CMDFLAG_NONE
}, 
 142     {"lastsave",1,CMDFLAG_NONE
}, 
 143     {"type",2,CMDFLAG_NONE
}, 
 144     {"flushdb",1,CMDFLAG_NONE
}, 
 145     {"flushall",1,CMDFLAG_NONE
}, 
 146     {"sort",-2,CMDFLAG_NONE
}, 
 147     {"info",1,CMDFLAG_RAWOUTPUT
}, 
 148     {"mget",-2,CMDFLAG_NONE
}, 
 149     {"expire",3,CMDFLAG_NONE
}, 
 150     {"expireat",3,CMDFLAG_NONE
}, 
 151     {"ttl",2,CMDFLAG_NONE
}, 
 152     {"slaveof",3,CMDFLAG_NONE
}, 
 153     {"debug",-2,CMDFLAG_NONE
}, 
 154     {"mset",-3,CMDFLAG_NONE
}, 
 155     {"msetnx",-3,CMDFLAG_NONE
}, 
 156     {"monitor",1,CMDFLAG_NONE
}, 
 157     {"multi",1,CMDFLAG_NONE
}, 
 158     {"exec",1,CMDFLAG_NONE
}, 
 159     {"discard",1,CMDFLAG_NONE
}, 
 160     {"hset",4,CMDFLAG_NONE
}, 
 161     {"hget",3,CMDFLAG_NONE
}, 
 162     {"hmset",-4,CMDFLAG_NONE
}, 
 163     {"hmget",-3,CMDFLAG_NONE
}, 
 164     {"hincrby",4,CMDFLAG_NONE
}, 
 165     {"hdel",3,CMDFLAG_NONE
}, 
 166     {"hlen",2,CMDFLAG_NONE
}, 
 167     {"hkeys",2,CMDFLAG_NONE
}, 
 168     {"hvals",2,CMDFLAG_NONE
}, 
 169     {"hgetall",2,CMDFLAG_NONE
}, 
 170     {"hexists",3,CMDFLAG_NONE
}, 
 171     {"config",-2,CMDFLAG_NONE
}, 
 172     {"subscribe",-2,CMDFLAG_NONE
}, 
 173     {"unsubscribe",-1,CMDFLAG_NONE
}, 
 174     {"psubscribe",-2,CMDFLAG_NONE
}, 
 175     {"punsubscribe",-1,CMDFLAG_NONE
}, 
 176     {"publish",3,CMDFLAG_NONE
}, 
 177     {NULL
,0,CMDFLAG_NONE
} 
 180 static int cliReadReply(int fd
); 
 183 static struct redisCommand 
*lookupCommand(char *name
) { 
 185     while(cmdTable
[j
].name 
!= NULL
) { 
 186         if (!strcasecmp(name
,cmdTable
[j
].name
)) return &cmdTable
[j
]; 
 192 static int cliConnect(void) { 
 193     char err
[ANET_ERR_LEN
]; 
 194     static int fd 
= ANET_ERR
; 
 196     if (fd 
== ANET_ERR
) { 
 197         fd 
= anetTcpConnect(err
,config
.hostip
,config
.hostport
); 
 198         if (fd 
== ANET_ERR
) { 
 199             fprintf(stderr
, "Could not connect to Redis at %s:%d: %s", config
.hostip
, config
.hostport
, err
); 
 202         anetTcpNoDelay(NULL
,fd
); 
 207 static sds 
cliReadLine(int fd
) { 
 208     sds line 
= sdsempty(); 
 218         } else if ((ret 
== 0) || (c 
== '\n')) { 
 221             line 
= sdscatlen(line
,&c
,1); 
 224     return sdstrim(line
,"\r\n"); 
 227 static int cliReadSingleLineReply(int fd
, int quiet
) { 
 228     sds reply 
= cliReadLine(fd
); 
 230     if (reply 
== NULL
) return 1; 
 232         printf("%s\n", reply
); 
 237 static void printStringRepr(char *s
, int len
) { 
 245         case '\n': printf("\\n"); break; 
 246         case '\r': printf("\\r"); break; 
 247         case '\t': printf("\\t"); break; 
 248         case '\a': printf("\\a"); break; 
 249         case '\b': printf("\\b"); break; 
 254                 printf("\\x%02x",(unsigned char)*s
); 
 262 static int cliReadBulkReply(int fd
) { 
 263     sds replylen 
= cliReadLine(fd
); 
 264     char *reply
, crlf
[2]; 
 267     if (replylen 
== NULL
) return 1; 
 268     bulklen 
= atoi(replylen
); 
 274     reply 
= zmalloc(bulklen
); 
 275     anetRead(fd
,reply
,bulklen
); 
 277     if (config
.raw_output 
|| !isatty(fileno(stdout
))) { 
 278         if (bulklen 
&& fwrite(reply
,bulklen
,1,stdout
) == 0) { 
 283         /* If you are producing output for the standard output we want 
 284          * a more interesting output with quoted characters and so forth */ 
 285         printStringRepr(reply
,bulklen
); 
 291 static int cliReadMultiBulkReply(int fd
) { 
 292     sds replylen 
= cliReadLine(fd
); 
 295     if (replylen 
== NULL
) return 1; 
 296     elements 
= atoi(replylen
); 
 297     if (elements 
== -1) { 
 303         printf("(empty list or set)\n"); 
 307         if (cliReadReply(fd
)) return 1; 
 313 static int cliReadReply(int fd
) { 
 316     if (anetRead(fd
,&type
,1) <= 0) exit(1); 
 320         cliReadSingleLineReply(fd
,0); 
 323         return cliReadSingleLineReply(fd
,0); 
 325         printf("(integer) "); 
 326         return cliReadSingleLineReply(fd
,0); 
 328         return cliReadBulkReply(fd
); 
 330         return cliReadMultiBulkReply(fd
); 
 332         printf("protocol error, got '%c' as reply type byte\n", type
); 
 337 static int selectDb(int fd
) { 
 342     if (config
.dbnum 
== 0) 
 346     cmd 
= sdscatprintf(cmd
,"SELECT %d\r\n",config
.dbnum
); 
 347     anetWrite(fd
,cmd
,sdslen(cmd
)); 
 348     anetRead(fd
,&type
,1); 
 349     if (type 
<= 0 || type 
!= '+') return 1; 
 350     retval 
= cliReadSingleLineReply(fd
,1); 
 357 static int cliSendCommand(int argc
, char **argv
, int repeat
) { 
 358     struct redisCommand 
*rc 
= lookupCommand(argv
[0]); 
 360     int fd
, j
, retval 
= 0; 
 364         fprintf(stderr
,"Unknown command '%s'\n",argv
[0]); 
 367     config
.raw_output 
= (rc
->flags 
& CMDFLAG_RAWOUTPUT
); 
 369     if ((rc
->arity 
> 0 && argc 
!= rc
->arity
) || 
 370         (rc
->arity 
< 0 && argc 
< -rc
->arity
)) { 
 371             fprintf(stderr
,"Wrong number of arguments for '%s'\n",rc
->name
); 
 375     if (!strcasecmp(rc
->name
,"shutdown")) shutdown 
= 1; 
 376     if (!strcasecmp(rc
->name
,"monitor")) config
.monitor_mode 
= 1; 
 377     if (!strcasecmp(rc
->name
,"subscribe") || 
 378         !strcasecmp(rc
->name
,"psubscribe")) config
.pubsub_mode 
= 1; 
 379     if ((fd 
= cliConnect()) == -1) return 1; 
 381     /* Select db number */ 
 382     retval 
= selectDb(fd
); 
 384         fprintf(stderr
,"Error setting DB num\n"); 
 389         /* Build the command to send */ 
 390         cmd 
= sdscatprintf(sdsempty(),"*%d\r\n",argc
); 
 391         for (j 
= 0; j 
< argc
; j
++) { 
 392             cmd 
= sdscatprintf(cmd
,"$%lu\r\n", 
 393                 (unsigned long)sdslen(argv
[j
])); 
 394             cmd 
= sdscatlen(cmd
,argv
[j
],sdslen(argv
[j
])); 
 395             cmd 
= sdscatlen(cmd
,"\r\n",2); 
 397         anetWrite(fd
,cmd
,sdslen(cmd
)); 
 400         while (config
.monitor_mode
) { 
 401             cliReadSingleLineReply(fd
,0); 
 404         if (config
.pubsub_mode
) { 
 405             printf("Reading messages... (press Ctrl-c to quit)\n"); 
 412         retval 
= cliReadReply(fd
); 
 414             return shutdown 
? 0 : retval
; 
 420 static int parseOptions(int argc
, char **argv
) { 
 423     for (i 
= 1; i 
< argc
; i
++) { 
 424         int lastarg 
= i
==argc
-1; 
 426         if (!strcmp(argv
[i
],"-h") && !lastarg
) { 
 427             char *ip 
= zmalloc(32); 
 428             if (anetResolve(NULL
,argv
[i
+1],ip
) == ANET_ERR
) { 
 429                 printf("Can't resolve %s\n", argv
[i
]); 
 434         } else if (!strcmp(argv
[i
],"-h") && lastarg
) { 
 436         } else if (!strcmp(argv
[i
],"-p") && !lastarg
) { 
 437             config
.hostport 
= atoi(argv
[i
+1]); 
 439         } else if (!strcmp(argv
[i
],"-r") && !lastarg
) { 
 440             config
.repeat 
= strtoll(argv
[i
+1],NULL
,10); 
 442         } else if (!strcmp(argv
[i
],"-n") && !lastarg
) { 
 443             config
.dbnum 
= atoi(argv
[i
+1]); 
 445         } else if (!strcmp(argv
[i
],"-a") && !lastarg
) { 
 446             config
.auth 
= argv
[i
+1]; 
 448         } else if (!strcmp(argv
[i
],"-i")) { 
 449             config
.interactive 
= 1; 
 457 static sds 
readArgFromStdin(void) { 
 459     sds arg 
= sdsempty(); 
 462         int nread 
= read(fileno(stdin
),buf
,1024); 
 464         if (nread 
== 0) break; 
 465         else if (nread 
== -1) { 
 466             perror("Reading from standard input"); 
 469         arg 
= sdscatlen(arg
,buf
,nread
); 
 474 static void usage() { 
 475     fprintf(stderr
, "usage: redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] [-i] cmd arg1 arg2 arg3 ... argN\n"); 
 476     fprintf(stderr
, "usage: echo \"argN\" | redis-cli [-h host] [-a authpw] [-p port] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n"); 
 477     fprintf(stderr
, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n"); 
 478     fprintf(stderr
, "example: cat /etc/passwd | redis-cli set my_passwd\n"); 
 479     fprintf(stderr
, "example: redis-cli get my_passwd\n"); 
 480     fprintf(stderr
, "example: redis-cli -r 100 lpush mylist x\n"); 
 481     fprintf(stderr
, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n"); 
 485 /* Turn the plain C strings into Sds strings */ 
 486 static char **convertToSds(int count
, char** args
) { 
 488   char **sds 
= zmalloc(sizeof(char*)*count
+1); 
 490   for(j 
= 0; j 
< count
; j
++) 
 491     sds
[j
] = sdsnew(args
[j
]); 
 496 static char **splitArguments(char *line
, int *argc
) { 
 498     char *current 
= NULL
; 
 499     char **vector 
= NULL
; 
 504         while(*p 
&& isspace(*p
)) p
++; 
 507             int inq
=0; /* set to 1 if we are in "quotes" */ 
 510             if (current 
== NULL
) current 
= sdsempty(); 
 513                     if (*p 
== '\\' && *(p
+1)) { 
 518                         case 'n': c 
= '\n'; break; 
 519                         case 'r': c 
= '\r'; break; 
 520                         case 't': c 
= '\t'; break; 
 521                         case 'b': c 
= '\b'; break; 
 522                         case 'a': c 
= '\a'; break; 
 523                         default: c 
= *p
; break; 
 525                         current 
= sdscatlen(current
,&c
,1); 
 526                     } else if (*p 
== '"') { 
 529                         current 
= sdscatlen(current
,p
,1); 
 544                         current 
= sdscatlen(current
,p
,1); 
 550             /* add the token to the vector */ 
 551             vector 
= zrealloc(vector
,((*argc
)+1)*sizeof(char*)); 
 552             vector
[*argc
] = current
; 
 561 #define LINE_BUFLEN 4096 
 566     while((line 
= linenoise("redis> ")) != NULL
) { 
 567         if (line
[0] != '\0') { 
 568             argv 
= splitArguments(line
,&argc
); 
 569             linenoiseHistoryAdd(line
); 
 571                 if (strcasecmp(argv
[0],"quit") == 0 || 
 572                     strcasecmp(argv
[0],"exit") == 0) 
 575                     cliSendCommand(argc
, argv
, 1); 
 577             /* Free the argument vector */ 
 578             for (j 
= 0; j 
< argc
; j
++) 
 582         /* linenoise() returns malloc-ed lines like readline() */ 
 588 int main(int argc
, char **argv
) { 
 591     struct redisCommand 
*rc
; 
 593     config
.hostip 
= "127.0.0.1"; 
 594     config
.hostport 
= 6379; 
 597     config
.interactive 
= 0; 
 598     config
.monitor_mode 
= 0; 
 599     config
.pubsub_mode 
= 0; 
 600     config
.raw_output 
= 0; 
 603     firstarg 
= parseOptions(argc
,argv
); 
 607     if (config
.auth 
!= NULL
) { 
 610         authargv
[0] = "AUTH"; 
 611         authargv
[1] = config
.auth
; 
 612         cliSendCommand(2, convertToSds(2, authargv
), 1); 
 615     if (argc 
== 0 || config
.interactive 
== 1) repl(); 
 617     argvcopy 
= convertToSds(argc
, argv
); 
 619     /* Read the last argument from stdandard input if needed */ 
 620     if ((rc 
= lookupCommand(argv
[0])) != NULL
) { 
 621       if (rc
->arity 
> 0 && argc 
== rc
->arity
-1) { 
 622         sds lastarg 
= readArgFromStdin(); 
 623         argvcopy
[argc
] = lastarg
; 
 628     return cliSendCommand(argc
, argvcopy
, config
.repeat
);