制作简易shell

要求:

  • 实现 管道 (也就是 |)
  • 实现 输入输出重定向(也就是 < > >>)
  • 实现 后台运行(也就是 & )
  • 实现 cd,要求支持能切换到绝对路径,相对路径和支持 cd -
  • 屏蔽一些信号(如 ctrl + c 不能终止)

1.将输入命令拆分存入二维字符指针分配的内存中

char **getcmd(int *p)
{
    char **cmd = (char **)malloc(sizeof(char *) * cmdsize);
    char *input = (char *)malloc(sizeof(char) * 1000);
    int len = 0, count = 0, pi = 0, i = 0;
    if (fgets(input, 1000, stdin) == NULL)
    {
        input[0] = 0;
    }

    int l = strlen(input);
    if (l == 0)
    {

        free(cmd);
        free(input);
        exit(0);
    }

    if (strcmp(input, "\n") == 0)
    {
        cmd[0] = (char *)malloc(5);
        strcpy(cmd[0], "\n");
        free(input);
        return cmd;
    }

    input[l - 1] = '\0';
    // add_history(input);
    while (len < l - 1)
    {
        while (input[len] == ' ')
        {
            len++;
            i++;
        }

        while (len < l - 1 && (input[len] != ' ' && input[len] != '|' && input[len] != '>' && input[len] != '<' && input[len] != '&'))
        {
            if (input[len] == '&')
            {
                bg = 1;
                input[len] = ' ';
            }

            len++;
        }
        cmd[count] = (char *)malloc(sizeof(char) * 20);
        realsize++;
        strncpy(cmd[count++], input + i, len - i);
        cmd[count - 1][len - i] = '\0';
        if (strcmp(cmd[count - 1], "~") == 0)
        {
            strcpy(cmd[count - 1], "/home/kangning");
        }
        // printf("|%s|\n",cmd[count-1]);
        while (input[len] == ' ' || input[len] == '&')
        {
            if (input[len] == '&')
            {
                bg = 1;
            }
            len++;
        }

        if (input[len] == '<')
        {
            in = count;
            len++;
        }
        else if (input[len] == '>')
        {
            len++;
            if (input[len] == '>')
            {
                len++;
                add = count;
            }
            else
                out = count;
        }
        if (input[len] == '|')
        {
            len++;
            p[pi++] = count;
        }

        while (input[len] == ' ' || input[len] == '<' || input[len] == '|' || input[len] == '>')
        {
            len++;
        }
        i = len;
    }
    free(input);
    input = NULL;
    return cmd;
}

2.没有管道时执行这个函数

void execcmd(char **cmd)
{
    pid_t pid;
    int status;
    pid = fork();

    switch (pid)
    {
    case -1:
        perror("fork");
        return;
    case 0:
        if (out)
        {
            int fd;

            if ((fd = open(cmd[out], O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
            {
                perror("open");
            }

            dup2(fd, STDOUT_FILENO);
            close(fd);
            free(cmd[out]);
            cmd[out] = NULL;
        }
        if (add)
        {
            int fd;

            if ((fd = open(cmd[add], O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
            {
                perror("open");
            }
            lseek(fd, -1, SEEK_END);
            dup2(fd, STDOUT_FILENO);
            close(fd);
            free(cmd[add]);
            cmd[add] = NULL;
        }
        execvp(cmd[0], cmd);
    default:
        if (!bg)
            mywait(pid, &status, 0);
    }
}

3.有管道时执行这个函数 

思路:命令用管道符号分开,分别执行,使用两个中间文件进行传输,还会用到swap函数来调换文件描述符,cutcmd函数将一维字符串分割成二维字符串

void piexec(char **cmd, int *p, int pipesize)
{
    char subcmd[10][50]={};
    int start = 0, i = 0, status;
    pid_t ppid;

    for (i = 0; i < pipesize; i++)
    {
        for (int j = start; j < p[i]; j++)
        {
            if (cmd[j])
            {
                strcat(subcmd[i], cmd[j]);
                strcat(subcmd[i], " ");
            }
        }
        start = p[i];
    }

    ppid = fork();
    if (ppid == -1)
    {
        perror("fork");
    }
    else if (ppid == 0)
    {
        pid_t pid;
        int sfd[2];
        for (i = 0; i <= pipesize; i++)
        {
            if (i == 0)
            {
                if ((pid = fork()) == -1)
                {
                    perror("fork");
                }
                else if (pid == 0)
                {
                    int fd[2];
                    if ((fd[0] = open("fifo1", O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if ((fd[1] = open("fifo2", O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }

                    sfd[1] = dup(STDOUT_FILENO);
                    dup2(fd[1], STDOUT_FILENO);
                    char **tmp = cutcmd(subcmd[i]);
                    execvp(cmd[0], tmp);
                }
                else
                {
                    mywait(pid, &status, 0);
                    dup2(sfd[1], STDOUT_FILENO);
                }
            }
            else if (i < pipesize)
            {
                if ((pid = fork()) == -1)
                {
                    perror("fork");
                }
                else if (pid == 0)
                {
                    int fd[2];
                    if ((fd[0] = open("fifo1", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if ((fd[1] = open("fifo2", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    swap(&fd[0], &fd[1]);

                    sfd[1] = dup(STDOUT_FILENO);
                    sfd[0] = dup(STDIN_FILENO);

                    dup2(fd[0], STDIN_FILENO);
                    dup2(fd[1], STDOUT_FILENO);
                    close(fd[0]);
                    close(fd[1]);
                    execvp(cmd[p[i]], cutcmd(subcmd[i]));
                }
                else
                {
                    mywait(pid, &status, 0);
                    dup2(sfd[0], STDIN_FILENO);
                    dup2(sfd[1], STDOUT_FILENO);
                }
            }
            else
            {
                if ((pid = fork()) == -1)
                {
                    perror("fork");
                }
                else if (pid == 0)
                {
                    int fd[2];
                    if ((fd[0] = open("fifo1", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if ((fd[1] = open("fifo2", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if (pipesize % 2)
                        swap(&fd[0], &fd[1]);

                    sfd[0] = dup(STDIN_FILENO);
                    int res = dup2(fd[0], STDIN_FILENO);
                    if (res == -1)
                    {
                        fprintf(stderr, "error dup2\n");
                        exit(1);
                    }
                    if (out)
                    {
                        int fd;
                        if ((fd = open(cmd[out], O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
                        {
                            perror("open");
                        }

                        dup2(fd, STDOUT_FILENO);
                        close(fd);
                        free(cmd[out]);
                        cmd[out] = NULL;
                    }
                    if (add)
                    {
                        int fd;

                        if ((fd = open(cmd[add], O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                        {
                            perror("open");
                        }
                        lseek(fd, -1, SEEK_END);
                        dup2(fd, STDOUT_FILENO);
                        close(fd);
                        free(cmd[add]);
                        cmd[add] = NULL;
                    }
                    close(fd[0]);
                    close(fd[1]);
                    printf("here\n");
                    execvp(cmd[start], cmd + start);
                }
                else
                {
                    mywait(pid, &status, 0);
                    dup2(sfd[0], STDIN_FILENO);
                }
            }
        }
    }
    else if (ppid > 0)
    {
        if (!bg)
        {
            mywait(ppid, &status, 0);
        }
        // while(1){
        //     sleep(1);
        //     printf("-");
        // }
        unlink("fifo1");
        unlink("fifo2");
    }
}

swap函数:

void swap(int *fd0, int *fd1)
{
    int temp = *fd0;
    *fd0 = *fd1;
    *fd1 = temp;
}

cutcmd函数:


char **cutcmd(char *input)
{
    char **cmd = (char **)malloc(sizeof(char *) * cmdsize);
    int len = 0, count = 0, pi = 0, i = 0;

    int l = strlen(input);
    while (len < l)
    {

        cmd[count] = (char *)malloc(sizeof(char) * 20);
        while (len < l && (input[len] != ' '))
        {
            len++;
        }
        strncpy(cmd[count++], input + i, len - i);
        if (strcmp(cmd[count - 1], "~") == 0)
        {
            strcpy(cmd[count - 1], "/home/kangning");
        }
        i = len;

        while (input[len] == ' ' || input[len] == '|')
        {
            i++;
            len++;
        }
    }
    return cmd;
}

4.主函数循环:

int main(void)
{
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, &set, NULL);
    setvbuf(stdout, NULL, _IONBF, 0);

    last = (char *)malloc(sizeof(char) * 100);

    while (1)
    {
        out = 0, add = 0, realsize = 0;
        int p[10] = {0}, sfd, fd, pipesize = 0;
        printf("# ");

        char **cmd = getcmd(p);
        if (strcmp(cmd[0], "\n") == 0)
        {
            free(cmd[0]);
            cmd[0] = NULL;
            free(cmd);
            cmd = NULL;
            continue;
        }

        int aaa = 0;
        if (in)
        {
            sfd = dup(STDIN_FILENO);
            fd = open(cmd[in], O_RDWR, S_IRUSR | S_IWUSR);
            if (dup2(fd, STDIN_FILENO) == -1)
            {
                fprintf(stderr, "error dup2\n");
                exit(1);
            }
            free(cmd[in]);
            cmd[in] = NULL;
        }

        if (strcmp(cmd[0], "exit") == 0)
        {
            exit(0);
        }
        else if (strcmp(cmd[0], "cd") == 0)
        {
            if (cmd[1] && strcmp(cmd[1], "-") == 0)
            {
                if (last)
                {

                    char *buf = (char *)malloc(sizeof(char) * 100);
                    getcwd(buf, 100);
                    printf("%s\n", last);

                    chdir(last);

                    strcpy(last, buf);
                }
                else
                { // last==NULL
                    printf("bash: cd: OLDPWD 未设定\n");
                }
            }
            else
            {

                getcwd(last, 100);
                chdir(cmd[1]);
            }

            continue;
        }

        for (int i = 0; i < 10; i++)
        {
            if (p[i])
                pipesize++;
        }
        if (p[0])
        {
            piexec(cmd, p, pipesize);
        }
        else
            execcmd(cmd);

        if (in)
        {
            if (dup2(sfd, STDIN_FILENO) == -1)
            {
                fprintf(stderr, "error dup2\n");
            }
            in = 0;
        }
        // printf("realsize = %d\n",realsize);
        for (int i = 0; i < realsize; i++)
        {
            if ((add && (i == add)) || (out && (i == out)) || (in && (i == in)))
                continue;
            free(cmd[i]);
            cmd[i] = NULL;
        }
        free(cmd);
        cmd = NULL;
    }
    free(last);
    return 0;
}

5.全局变量解释:in 重定向输入,out 重定向输出,add 重定向追加,pipesize 管道数量,cmdsize 二维字符数组中的一维数量

6.mywait函数回收子进程

void mywait(pid_t pid, int *status, int options)
{
    waitpid(pid, status, options);

    if (WIFSIGNALED(*status))
    {
        printf("\nshell be killed by %d", WTERMSIG(*status));
        if (WCOREDUMP(*status))
        {
            printf("\n退出 (核心已转储)");
        }
    }
}

7.源代码

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <errno.h>

int in = 0, out = 0, add = 0, bg = 0, cmdsize = 100, realsize = 0;
char *last;

char **getcmd(int *p);
char **cutcmd(char *input);
void execcmd(char **cmd);
void swap(int *fd0, int *fd1);
void piexec(char **cmd, int *p, int pipesize);
void piexec1(char **cmd, int *p, int pipesize);
void mywait(pid_t pid, int *status, int options);
void newexec(char **cmd, int *p, int pipesize, int start, int head);

int main(void)
{
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigprocmask(SIG_BLOCK, &set, NULL);
    setvbuf(stdout, NULL, _IONBF, 0);

    last = (char *)malloc(sizeof(char) * 100);

    while (1)
    {
        out = 0, add = 0, realsize = 0;
        int p[10] = {0}, sfd, fd, pipesize = 0;
        printf("# ");

        char **cmd = getcmd(p);
        if (strcmp(cmd[0], "\n") == 0)
        {
            free(cmd[0]);
            cmd[0] = NULL;
            free(cmd);
            cmd = NULL;
            continue;
        }

        int aaa = 0;
        if (in)
        {
            sfd = dup(STDIN_FILENO);
            fd = open(cmd[in], O_RDWR, S_IRUSR | S_IWUSR);
            if (dup2(fd, STDIN_FILENO) == -1)
            {
                fprintf(stderr, "error dup2\n");
                exit(1);
            }
            free(cmd[in]);
            cmd[in] = NULL;
        }

        if (strcmp(cmd[0], "exit") == 0)
        {
            exit(0);
        }
        else if (strcmp(cmd[0], "cd") == 0)
        {
            if (cmd[1] && strcmp(cmd[1], "-") == 0)
            {
                if (last)
                {

                    char *buf = (char *)malloc(sizeof(char) * 100);
                    getcwd(buf, 100);
                    printf("%s\n", last);

                    chdir(last);

                    strcpy(last, buf);
                }
                else
                { // last==NULL
                    printf("bash: cd: OLDPWD 未设定\n");
                }
            }
            else
            {

                getcwd(last, 100);
                chdir(cmd[1]);
            }

            continue;
        }

        for (int i = 0; i < 10; i++)
        {
            if (p[i])
                pipesize++;
        }
        if (p[0])
        {
            piexec(cmd, p, pipesize);
        }
        else
            execcmd(cmd);

        if (in)
        {
            if (dup2(sfd, STDIN_FILENO) == -1)
            {
                fprintf(stderr, "error dup2\n");
            }
            in = 0;
        }
        // printf("realsize = %d\n",realsize);
        for (int i = 0; i < realsize; i++)
        {
            if ((add && (i == add)) || (out && (i == out)) || (in && (i == in)))
                continue;
            free(cmd[i]);
            cmd[i] = NULL;
        }
        free(cmd);
        cmd = NULL;
    }
    free(last);
    return 0;
}

char **getcmd(int *p)
{
    char **cmd = (char **)malloc(sizeof(char *) * cmdsize);
    char *input = (char *)malloc(sizeof(char) * 1000);
    int len = 0, count = 0, pi = 0, i = 0;
    if (fgets(input, 1000, stdin) == NULL)
    {
        input[0] = 0;
    }

    int l = strlen(input);
    if (l == 0)
    {

        free(cmd);
        free(input);
        exit(0);
    }

    if (strcmp(input, "\n") == 0)
    {
        cmd[0] = (char *)malloc(5);
        strcpy(cmd[0], "\n");
        free(input);
        return cmd;
    }

    input[l - 1] = '\0';
    // add_history(input);
    while (len < l - 1)
    {
        while (input[len] == ' ')
        {
            len++;
            i++;
        }

        while (len < l - 1 && (input[len] != ' ' && input[len] != '|' && input[len] != '>' && input[len] != '<' && input[len] != '&'))
        {
            if (input[len] == '&')
            {
                bg = 1;
                input[len] = ' ';
            }

            len++;
        }
        cmd[count] = (char *)malloc(sizeof(char) * 20);
        realsize++;
        strncpy(cmd[count++], input + i, len - i);
        cmd[count - 1][len - i] = '\0';
        if (strcmp(cmd[count - 1], "~") == 0)
        {
            strcpy(cmd[count - 1], "/home/kangning");
        }
        // printf("|%s|\n",cmd[count-1]);
        while (input[len] == ' ' || input[len] == '&')
        {
            if (input[len] == '&')
            {
                bg = 1;
            }
            len++;
        }

        if (input[len] == '<')
        {
            in = count;
            len++;
        }
        else if (input[len] == '>')
        {
            len++;
            if (input[len] == '>')
            {
                len++;
                add = count;
            }
            else
                out = count;
        }
        if (input[len] == '|')
        {
            len++;
            p[pi++] = count;
        }

        while (input[len] == ' ' || input[len] == '<' || input[len] == '|' || input[len] == '>')
        {
            len++;
        }
        i = len;
    }
    free(input);
    input = NULL;
    return cmd;
}

char **cutcmd(char *input)
{
    char **cmd = (char **)malloc(sizeof(char *) * cmdsize);
    int len = 0, count = 0, pi = 0, i = 0;

    int l = strlen(input);
    while (len < l)
    {

        cmd[count] = (char *)malloc(sizeof(char) * 20);
        while (len < l && (input[len] != ' '))
        {
            len++;
        }
        strncpy(cmd[count++], input + i, len - i);
        if (strcmp(cmd[count - 1], "~") == 0)
        {
            strcpy(cmd[count - 1], "/home/kangning");
        }
        i = len;

        while (input[len] == ' ' || input[len] == '|')
        {
            i++;
            len++;
        }
    }
    return cmd;
}

void execcmd(char **cmd)
{
    pid_t pid;
    int status;
    pid = fork();

    switch (pid)
    {
    case -1:
        perror("fork");
        return;
    case 0:
        if (out)
        {
            int fd;

            if ((fd = open(cmd[out], O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
            {
                perror("open");
            }

            dup2(fd, STDOUT_FILENO);
            close(fd);
            free(cmd[out]);
            cmd[out] = NULL;
        }
        if (add)
        {
            int fd;

            if ((fd = open(cmd[add], O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
            {
                perror("open");
            }
            lseek(fd, -1, SEEK_END);
            dup2(fd, STDOUT_FILENO);
            close(fd);
            free(cmd[add]);
            cmd[add] = NULL;
        }
        execvp(cmd[0], cmd);
    default:
        if (!bg)
            mywait(pid, &status, 0);
    }
}

void swap(int *fd0, int *fd1)
{
    int temp = *fd0;
    *fd0 = *fd1;
    *fd1 = temp;
}

void mywait(pid_t pid, int *status, int options)
{
    waitpid(pid, status, options);

    if (WIFSIGNALED(*status))
    {
        printf("\nshell be killed by %d", WTERMSIG(*status));
        if (WCOREDUMP(*status))
        {
            printf("\n退出 (核心已转储)");
        }
    }
}

void piexec(char **cmd, int *p, int pipesize)
{
    char subcmd[10][50]={};
    int start = 0, i = 0, status;
    pid_t ppid;

    for (i = 0; i < pipesize; i++)
    {
        for (int j = start; j < p[i]; j++)
        {
            if (cmd[j])
            {
                strcat(subcmd[i], cmd[j]);
                strcat(subcmd[i], " ");
            }
        }
        start = p[i];
    }

    ppid = fork();
    if (ppid == -1)
    {
        perror("fork");
    }
    else if (ppid == 0)
    {
        pid_t pid;
        int sfd[2];
        for (i = 0; i <= pipesize; i++)
        {
            if (i == 0)
            {
                if ((pid = fork()) == -1)
                {
                    perror("fork");
                }
                else if (pid == 0)
                {
                    int fd[2];
                    if ((fd[0] = open("fifo1", O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if ((fd[1] = open("fifo2", O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }

                    sfd[1] = dup(STDOUT_FILENO);
                    dup2(fd[1], STDOUT_FILENO);
                    char **tmp = cutcmd(subcmd[i]);
                    execvp(cmd[0], tmp);
                }
                else
                {
                    mywait(pid, &status, 0);
                    dup2(sfd[1], STDOUT_FILENO);
                }
            }
            else if (i < pipesize)
            {
                if ((pid = fork()) == -1)
                {
                    perror("fork");
                }
                else if (pid == 0)
                {
                    int fd[2];
                    if ((fd[0] = open("fifo1", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if ((fd[1] = open("fifo2", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    swap(&fd[0], &fd[1]);

                    sfd[1] = dup(STDOUT_FILENO);
                    sfd[0] = dup(STDIN_FILENO);

                    dup2(fd[0], STDIN_FILENO);
                    dup2(fd[1], STDOUT_FILENO);
                    close(fd[0]);
                    close(fd[1]);
                    execvp(cmd[p[i]], cutcmd(subcmd[i]));
                }
                else
                {
                    mywait(pid, &status, 0);
                    dup2(sfd[0], STDIN_FILENO);
                    dup2(sfd[1], STDOUT_FILENO);
                }
            }
            else
            {
                if ((pid = fork()) == -1)
                {
                    perror("fork");
                }
                else if (pid == 0)
                {
                    int fd[2];
                    if ((fd[0] = open("fifo1", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if ((fd[1] = open("fifo2", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                    {
                        perror("open");
                    }
                    if (pipesize % 2)
                        swap(&fd[0], &fd[1]);

                    sfd[0] = dup(STDIN_FILENO);
                    int res = dup2(fd[0], STDIN_FILENO);
                    if (res == -1)
                    {
                        fprintf(stderr, "error dup2\n");
                        exit(1);
                    }
                    if (out)
                    {
                        int fd;
                        if ((fd = open(cmd[out], O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR)) == -1)
                        {
                            perror("open");
                        }

                        dup2(fd, STDOUT_FILENO);
                        close(fd);
                        free(cmd[out]);
                        cmd[out] = NULL;
                    }
                    if (add)
                    {
                        int fd;

                        if ((fd = open(cmd[add], O_RDWR | O_CREAT, S_IWUSR | S_IRUSR)) == -1)
                        {
                            perror("open");
                        }
                        lseek(fd, -1, SEEK_END);
                        dup2(fd, STDOUT_FILENO);
                        close(fd);
                        free(cmd[add]);
                        cmd[add] = NULL;
                    }
                    close(fd[0]);
                    close(fd[1]);
                    printf("here\n");
                    execvp(cmd[start], cmd + start);
                }
                else
                {
                    mywait(pid, &status, 0);
                    dup2(sfd[0], STDIN_FILENO);
                }
            }
        }
    }
    else if (ppid > 0)
    {
        if (!bg)
        {
            mywait(ppid, &status, 0);
        }
        // while(1){
        //     sleep(1);
        //     printf("-");
        // }
        unlink("fifo1");
        unlink("fifo2");
    }
}

写在最后:程序还有一些不完善的地方,不过还能忍,可以接受

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倚风听雨.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值