神奇的sh:管道操作|原理 ,實現一個支援管道操作的grep
阿新 • • 發佈:2019-01-28
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