神奇的sh:管道操作|原理 ,实现一个支持管道操作的grep

C程序的输入有参数和标准输入,shell管道是将上一个程序的stdout重定向 到下一个程序的stdin,跟程序参数无关。

echo无法使用管道,因为它打印参数,而不从stdin中读取数据。

支持管道的C程序示例,它将打印参数和stdin的内容:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

int main(int argc, char *argv[]){
    #define BUF_LEN 256
    static char buf[BUF_LEN];
    for (int i = 0; i < argc; ++i){
        printf("[from args][%s]\n", argv[i]);
    }
    while (fgets(buf, BUF_LEN, stdin) != NULL){
        printf("[from stdin][%s]\n", buf);
    }
    return 0;
}

在命令行中这样使用可以测试:

./bin arg1 arg2 < file

echo不支持管道,下面实现一个支持管道的echo:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>

int main(int argc, char *argv[]){
    #define BUF_LEN 1024
    static char buf[BUF_LEN];
    if (argc == 1){
        while (fgets(buf, BUF_LEN, stdin) != NULL){
            printf("%s ", buf);
        }
    }
    else
    {
        for (int i = 1; i < argc; ++i){
            printf("%s ", argv[i]);
        }
    }
    printf("\n");
    return 0;
}

当没有指定参数时,将从stdin中去读取数据来显示。于是我们可以这样使用:

echo -n data | ./my_echo
echo -n data | echo
你会发现第一行会输出“data”和换行,第二行会输出换行,也就是说echo没有处理stdin的内容,只处理了argv。

再想想grep,它支持管道的思想和上面的程序是一样的:如果参数满足某某条件,就从stdin中读取。grep的条件是“如果没有指定输入文件”,而my_echo的条件是“如果没有指定要打印的字符串”。有了这个思路,我们可以自己实现一个简陋的grep命令(暂未实现对输入文件的grep & 只考虑英文):

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdarg.h>

/**
 * 验证长度为len的字符串pattern的部分匹配值是不是n
 */
int is_partial_match_equal(int n, const char *pattern, size_t len){
    for (size_t i = 0, j = len - n; i < len && j < len; ++i, ++j){
        if (pattern[i] != pattern[j]){
            return 0;
        }
    }
    return 1;
}
/**
 * 获取长度是len的字符串pattern的部分匹配值
 * notice:穷举法实现
 */
int get_partial_match_cnt(const char *pattern, size_t len){
    if (len < 2) return 0;
    for (size_t match = len - 1; match >= 1; --match){
        if (is_partial_match_equal(match, pattern, len)){
            return match;
        }
    }
    return 0;
}
/**
 * next数组的长度至少是pattern的长度。
 * next[i]表示pattern[i]没有匹配上时应该往后移动的数量。
 */
int get_next(int next[], const char *pattern, size_t len){
    if (len < 2){ 
        return 0;
    }
    for (size_t match_len = 1; match_len <= len; ++match_len){
        next[match_len - 1] = match_len - get_partial_match_cnt(pattern, match_len);
    }
    return 0;
}
#ifdef DEBUG
void debug_print_next_array(int next[], size_t len){
    printf("Next array:\n");
    for (size_t i = 0; i < len; ++i){
        printf("next[%d]=%d\n", (int)i, next[i]);
    }
    printf("End\n");
}
void debug_log(const char *fmt, ...){
    va_list mark;
    va_start(mark, fmt);
    vprintf(fmt, mark);
    va_end(mark);
}
#else
void debug_print_next_array(int next[], size_t len){}
void debug_log(const char *fmt, ...){}
#endif
/**
 * KMP算法
 * 返回正数表示从str[i]开始,str和pattern匹配上了。
 * 返回-1表示无法匹配。
 */
int KMP(const char *str, const char *pattern){
    debug_log("KMP(%s, %s)\n", str, pattern);
    size_t len = strlen(pattern);
    int *next = (int *)malloc(sizeof(int) * len);
    get_next(next, pattern, len);
    debug_print_next_array(next, len);

    unsigned int idx_str = 0, idx_pat = 0; 
    while (str[idx_str] && pattern[idx_pat]){
        debug_log("Compare(%c, %c)\n", str[idx_str], pattern[idx_pat]);
        if (str[idx_str] == pattern[idx_pat]){
            ++idx_str;
            ++idx_pat;
        }
        else
        {
            idx_str += next[idx_pat];
            idx_pat = 0;
        }
    }
    free(next);
    
    if (!pattern[idx_pat]){
        int pos = (int)(idx_str - idx_pat);
        debug_log("Position = %d\n", pos);
        return pos;
    }else{ // (!str[idx_str]){
        debug_log("Pattern not found\n");
        return -1;
    }
}
/**
 * grep的实现
 * 返回1表示出错,0表示正确
 */
#define BUF_LEN 1024 // 假设一行不超过1024个ascii字符
static char buf[BUF_LEN];
int grep(int argc, char *argv[]){
    // parse [OPTIONS]
    // parse PATTERN
    // parse [FILES]
    // notice:规定实现的grep不包含options,pattern在argv[1]中。如果没有[FILES]则从stdin中读取数据。
    if (argc < 2){
        return 1;
    }
    const char *pattern = argv[1];
    if (argc == 2){
        // 从stdin读取数据
        while (fgets(buf, BUF_LEN, stdin) != NULL){
            if (KMP(buf, pattern) != -1){
                printf("%s", buf);
            }
        }
    }
    else
    {
        // 从文件读取数据
        for (int idx_file = 2; idx_file < argc; ++idx_file){
            const char *file_name = argv[idx_file];
            // open file
            // read lines
            // close file
            printf("Parsing file %s: function unfinished!\n", file_name);
        }
    }
    return 0;
}
int main(int argc, char *argv[]){
    debug_log("In debug mode\n");
    return grep(argc, argv);
}



/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */

编译make release:

BIN=my_grep
debug:test.cpp
    gcc -DDEBUG -lstdc++ $< -o $(BIN) -Wall
release:test.cpp clean
    gcc -lstdc++ $< -o $(BIN) -Wall
    chmod u+x $(BIN)

.PHONY:clean
clean:
    rm $(BIN)
然后再写个脚本来测试一下子:

pattern=abc
echo Pattern is $pattern
cat data | ./my_grep $pattern

echo "Runn over, now checking..."
if [ $? -eq 0 ]; then
    echo "grep ok";
else
    echo "grep failed";
fi

测试数据放在data文件中:

xxxabcabcxxxxx
xxxxabcxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxx
abcxxxxxxxxxxx
xxxxxxxxxabc





更新----------------------------------------------------------------增加grep对文件的处理。如果文件打开失败,my_grep将返回1表示错误。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdarg.h>

/**
 * 验证长度为len的字符串pattern的部分匹配值是不是n
 */
int is_partial_match_equal(int n, const char *pattern, size_t len){
    for (size_t i = 0, j = len - n; i < len && j < len; ++i, ++j){
        if (pattern[i] != pattern[j]){
            return 0;
        }
    }
    return 1;
}
/**
 * 获取长度是len的字符串pattern的部分匹配值
 * notice:穷举法实现
 */
int get_partial_match_cnt(const char *pattern, size_t len){
    if (len < 2) return 0;
    for (size_t match = len - 1; match >= 1; --match){
        if (is_partial_match_equal(match, pattern, len)){
            return match;
        }
    }
    return 0;
}
/**
 * next数组的长度至少是pattern的长度。
 * next[i]表示pattern[i]没有匹配上时应该往后移动的数量。
 */
int get_next(int next[], const char *pattern, size_t len){
    if (len < 2){ 
        return 0;
    }
    for (size_t match_len = 1; match_len <= len; ++match_len){
        next[match_len - 1] = match_len - get_partial_match_cnt(pattern, match_len);
    }
    return 0;
}
#ifdef DEBUG
void debug_print_next_array(int next[], size_t len){
    printf("Next array:\n");
    for (size_t i = 0; i < len; ++i){
        printf("next[%d]=%d\n", (int)i, next[i]);
    }
    printf("End\n");
}
void debug_log(const char *fmt, ...){
    va_list mark;
    va_start(mark, fmt);
    vprintf(fmt, mark);
    va_end(mark);
}
#else
void debug_print_next_array(int next[], size_t len){}
void debug_log(const char *fmt, ...){}
#endif
/**
 * KMP算法
 * 返回正数表示从str[i]开始,str和pattern匹配上了。
 * 返回-1表示无法匹配。
 */
int KMP(const char *str, const char *pattern){
    debug_log("KMP(%s, %s)\n", str, pattern);
    size_t len = strlen(pattern);
    int *next = (int *)malloc(sizeof(int) * len);
    get_next(next, pattern, len);
    debug_print_next_array(next, len);

    unsigned int idx_str = 0, idx_pat = 0; 
    while (str[idx_str] && pattern[idx_pat]){
        debug_log("Compare(%c, %c)\n", str[idx_str], pattern[idx_pat]);
        if (str[idx_str] == pattern[idx_pat]){
            ++idx_str;
            ++idx_pat;
        }
        else
        {
            idx_str += next[idx_pat];
            idx_pat = 0;
        }
    }
    free(next);
    
    if (!pattern[idx_pat]){
        int pos = (int)(idx_str - idx_pat);
        debug_log("Position = %d\n", pos);
        return pos;
    }else{ // (!str[idx_str]){
        debug_log("Pattern not found\n");
        return -1;
    }
}
/**
 * 从文件中读取每一行,进行grep
 */
int grep_file(FILE *file, char *buf, int buf_len, const char *pattern){
    while (fgets(buf, buf_len, file) != NULL){
        if (KMP(buf, pattern) != -1){
            printf("%s", buf);
        }
    }
    return 0;
}
/**
 * grep的实现
 * 返回1表示出错,0表示正确
 */
#define BUF_LEN 1024 // 假设一行不超过1024个ascii字符
static char buf[BUF_LEN];
int grep(int argc, char *argv[]){
    // parse [OPTIONS]
    // parse PATTERN
    // parse [FILES]
    // notice:规定实现的grep不包含options,pattern在argv[1]中。如果没有[FILES]则从stdin中读取数据。
    int ret = 0;
    if (argc < 2){
        return 1;
    }
    const char *pattern = argv[1];
    if (argc == 2){
        grep_file(stdin, buf, BUF_LEN, pattern);
    }
    else
    {
        // 从文件读取数据
        for (int idx_file = 2; idx_file < argc; ++idx_file){
            const char *file_name = argv[idx_file];
            FILE * file = fopen(file_name, "r");
            if (file == NULL){
                ret = 1;
                continue;
            }
            grep_file(file, buf, BUF_LEN, pattern);
            fclose(file);
        }
    }
    return ret;
}
int main(int argc, char *argv[]){
    debug_log("Warning: In debug mode\n");
    return grep(argc, argv);
}



/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */

makefile:

BIN=my_grep

default:release

.PHONY:debug
debug:test.cpp
    gcc -DDEBUG -lstdc++ $< -o $(BIN) -Wall
    
.PHONY:release
release:test.cpp clean
    gcc -lstdc++ $< -o $(BIN) -Wall
    chmod u+x $(BIN)

.PHONY:clean
clean:
    if [ -f $(BIN) ]; then rm $(BIN); fi

测试脚本:

check_last_cmd()
{
    last_cmd_res=$?
    echo
    echo "Runn over, now checking..."

    if [ $last_cmd_res -eq 0 ]; then
        echo "grep ok";
    else
        echo "grep failed";
    fi
}

pattern=abc

echo Pattern is $pattern
echo Testing shell pipe 
echo

cat data | ./my_grep $pattern
check_last_cmd

echo
echo Testing my_grep\'s handling files
echo
./my_grep $pattern data data2
check_last_cmd


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值