背景: 我用若干个不同的配置文件大同小异,只是几个参数不同。如果一个个手工去修改,非常繁琐。于是有下面的:
/*in-place replace mth line 's nth word with val, keeping spaces as original as possible. not support tab, you may use: sed -i 's/\t/ /g' inputfile usage example: find . -name config.mak -exec ./file-inplace {} 260 3 '2#' \; if on linux, keep PC dos ending: find . -name config.mak -exec unix2dos {} \; author: ludi 2013.12 */ #include <stdio.h> #include <stdlib.h> #include <string.h> char line[9192]; int word_len(char *w) { int len = 0; /*while(!isspace(*w)){++w; ++len;} <-- can't handle non-ascii*/ for(;*w; ++w){ if(' ' == *w || '\n' == *w || '\t' == *w){break;} ++len; } return len; } int work(char *line, int n, char *val) { char *ptr = line; int wlen, vlen, s = 0, cnt = 0; for(; *ptr; ++ptr) { if(*ptr!= ' '){ s = 1; }else if(1 == s){ ++cnt; s = 0; } if(cnt == n){ while(*ptr == ' ')++ptr; wlen = word_len(ptr); vlen = strlen(val); if(vlen > wlen){ if(ptr[vlen] != ' ')memmove(ptr+vlen+1, ptr+wlen+1, strlen(ptr+wlen+1)); strncpy(ptr, val, vlen); ptr[vlen] = ptr[vlen] ? ' ': '\n'; }else{ strncpy(ptr, val, vlen); ptr += vlen; while(*ptr != ' ' && *ptr != '\n' && *ptr != '\t')*ptr++ = ' '; } break; } } } int main(int ac, char **av) { int mth, nth; char *val, *tmpname = "xx.tmp"; FILE *fp, *tp; int i, ch; if(ac < 5)return printf("usage: file-in-replace inputfile mth nth val\n"); fp = fopen(av[1], "r+"); tp = fopen(tmpname, "w+"); if(!fp||!tp){return printf("can't open %s", av[1]);} mth = atoi(av[2]); nth = atoi(av[3]); val = av[4]; for(i = 1; ; ++i){ memset(line, 0, sizeof(line)); if(!fgets(line, sizeof(line), fp)){break;} if(i == mth){ work(line, nth-1, val); } fprintf(tp, "%s", line); } rewind(fp); rewind(tp); /*while(!feof(tp))fputc(fgetc(tp), fp);*/ while((ch = fgetc(tp)) != EOF){ fputc(ch, fp); } fclose(fp); fclose(tp); remove(tmpname); return 0; }
最先我也尝试过gawk, sed, 发现gawk 不能保留空格,这影响源文件的对齐。于是抽象了一个line-in-replace, 用这样的方案:
FILE=def.txt NR=160 COL=2 VAL=259
TMP=$(head -n $NR $FILE | tail -n 1 | ./line-in-replace $COL $VAL)
LANG=C sed -i -e "$NR s/.*/$TMP/" $FILE
那个LANG是为了处理中文,必须有,不然sed不正常。
后来觉得这三行复制粘贴也太麻烦,于是就整出了上面的file-in-replace。
贴在这里是为了记录我遇到的问题,一遍以后查。