#include #include #include #include #include #include #include #include /* returns true if '%' occurs exactly once within pattern */ bool valid_pattern(const char* pattern) { size_t count = 0; while (*pattern) { if (*pattern == '%') { ++count; if (count == 2) return false; } ++pattern; } return count == 1; } /* extract the stem, i.e. the part matched by '%' in pattern from the given filename; return 0 if the filename does not match the pattern */ char* extract_stem(const char* pattern, const char* filename) { const char* pp = pattern; const char* fp = filename; while (*pp && *pp != '%' && *fp == *pp) { ++pp; ++fp; } if (*pp != '%') return 0; const char* pp2 = pp + strlen(pp) - 1; const char* fp2 = fp + strlen(fp) - 1; while (*pp2 == *fp2 && pp2 > pp && fp2 > fp) { --pp2; --fp2; } if (pp2 != pp) return 0; size_t stem_len = fp2 + 1 - fp; char* stem = malloc(stem_len + 1); if (!stem) return 0; strncpy(stem, fp, stem_len); stem[stem_len] = 0; return stem; } /* generate string from the pattern by substituting '%' by the stem; a null pointer is returned in case of errors */ char* apply_stem(const char* pattern, const char* stem) { size_t len = strlen(pattern) - 1 + strlen(stem); char* s = malloc(len+1); if (!s) return 0; char* cp = s; while (*pattern && *pattern != '%') { *cp++ = *pattern++; } ++pattern; /* skip '%' */ while (*stem) { *cp++ = *stem++; } while (*pattern) { *cp++ = *pattern++; } *cp++ = 0; return s; } int main(int argc, char** argv) { const char* cmdname = *argv++; --argc; const char* usage = "Usage: %s delimiter command ... delimiter " "in-pattern out-pattern {file}\n"; if (argc < 6) { fprintf(stderr, usage, cmdname); exit(1); } const char* delimiter = *argv++; --argc; char** command = argv++; --argc; while (argc > 0 && strcmp(*argv, delimiter) != 0) { --argc; ++argv; } if (argc == 0) { fprintf(stderr, "%s: terminating delimiter '%s' not found\n", cmdname, delimiter); fprintf(stderr, usage, cmdname); exit(1); } *argv++ = 0; --argc; if (argc < 3) { fprintf(stderr, "%s; too few arguments left: %d\n", cmdname, argc); fprintf(stderr, usage, cmdname); exit(1); } const char* inpattern = *argv++; --argc; const char* outpattern = *argv++; --argc; if (!valid_pattern(inpattern) || !valid_pattern(outpattern)) { fprintf(stderr, "%s: patterns must include exactly one '%%'\n", cmdname); exit(1); } char** files = argv; unsigned int file_count = argc; char* stem[argc]; unsigned int stem_index = 0; while (argc > 0) { stem[stem_index] = extract_stem(inpattern, *argv); if (!stem[stem_index]) { fprintf(stderr, "%s: %s does not match '%s'\n", cmdname, *argv, inpattern); exit(1); } --argc; ++argv; ++stem_index; } pid_t pid[file_count]; for (unsigned int i = 0; i < file_count; ++i) { pid[i] = fork(); if (pid[i] < 0) { perror("fork"); exit(1); } if (pid[i] == 0) { int fdin = open(files[i], O_RDONLY); if (fdin < 0) { perror(files[i]); exit(1); } if (dup2(fdin, 0) < 0 || close(fdin) < 0) { perror("dup2 or close"); exit(1); } char* outfile = apply_stem(outpattern, stem[i]); free(stem[i]); /* no longer needed */ int fdout = open(outfile, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fdout < 0) { perror(outfile); exit(1); } if (dup2(fdout, 1) < 0 || close(fdout) < 0) { perror("dup2 or close"); exit(1); } execvp(command[0], command); perror(command[0]); exit(1); } } unsigned int exit_code = 0; for (unsigned int i = 0; i < file_count; ++i) { int stat; if (waitpid(pid[i], &stat, 0) < 0) { perror("wait"); exit(1); } if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0) { fprintf(stderr, "%s: %s terminated not successfuly for %s\n", cmdname, command[0], files[i]); ++exit_code; } } exit(exit_code); }