From 8e26716a5a33fc4a7c6f5f0a72356523a8ca9119 Mon Sep 17 00:00:00 2001 From: Connor Lane Smith Date: Mon, 23 May 2011 02:36:34 +0100 Subject: [PATCH] initial commit --- LICENSE | 21 ++++++++++++ Makefile | 31 +++++++++++++++++ basename.1 | 14 ++++++++ basename.c | 30 +++++++++++++++++ cat.1 | 10 ++++++ cat.c | 36 ++++++++++++++++++++ config.mk | 9 +++++ echo.1 | 14 ++++++++ echo.c | 23 +++++++++++++ false.1 | 8 +++++ false.c | 8 +++++ grep.1 | 43 ++++++++++++++++++++++++ grep.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tee.1 | 14 ++++++++ tee.c | 36 ++++++++++++++++++++ touch.1 | 22 +++++++++++++ touch.c | 58 ++++++++++++++++++++++++++++++++ true.1 | 8 +++++ true.c | 8 +++++ util.c | 22 +++++++++++++ util.h | 3 ++ wc.1 | 28 ++++++++++++++++ wc.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++++ 23 files changed, 632 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 basename.1 create mode 100644 basename.c create mode 100644 cat.1 create mode 100644 cat.c create mode 100644 config.mk create mode 100644 echo.1 create mode 100644 echo.c create mode 100644 false.1 create mode 100644 false.c create mode 100644 grep.1 create mode 100644 grep.c create mode 100644 tee.1 create mode 100644 tee.c create mode 100644 touch.1 create mode 100644 touch.c create mode 100644 true.1 create mode 100644 true.c create mode 100644 util.c create mode 100644 util.h create mode 100644 wc.1 create mode 100644 wc.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eca3868 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT/X Consortium License + +© 2011 Connor Lane Smith + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c4f437e --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +include config.mk + +SRC = basename.c cat.c echo.c false.c grep.c tee.c touch.c true.c wc.c +OBJ = $(SRC:.c=.o) util.o +BIN = $(SRC:.c=) +MAN = $(SRC:.c=.1) + +all: $(BIN) + +$(OBJ): util.h +$(BIN): util.o + +.o: + @echo CC -o $@ + @$(CC) -o $@ $< util.o $(LDFLAGS) + +.c.o: + @echo CC -c $< + @$(CC) -c $< $(CFLAGS) + +dist: clean + @echo creating dist tarball + @mkdir -p sbase-$(VERSION) + @cp LICENSE Makefile config.mk $(SRC) $(MAN) util.c util.h sbase-$(VERSION) + @tar -cf sbase-$(VERSION).tar sbase-$(VERSION) + @gzip sbase-$(VERSION).tar + @rm -rf sbase-$(VERSION) + +clean: + @echo cleaning + @rm -f $(BIN) $(OBJ) diff --git a/basename.1 b/basename.1 new file mode 100644 index 0000000..8f9b14f --- /dev/null +++ b/basename.1 @@ -0,0 +1,14 @@ +.TH BASENAME 1 sbase\-VERSION +.SH NAME +basename \- strip directory from filename +.SH SYNOPSIS +.B basename +.I string +.RI [ suffix ] +.SH DESCRIPTION +.B basename +prints to stdout the +.I string +with any leading directory components, and the +.IR suffix , +removed. diff --git a/basename.c b/basename.c new file mode 100644 index 0000000..628b834 --- /dev/null +++ b/basename.c @@ -0,0 +1,30 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include "util.h" + +int +main(int argc, char *argv[]) +{ + char *str = argv[1]; + size_t n, i = 0; + + if(argc < 2) + eprintf("usage: %s string [suffix]\n", argv[0]); + if(str[0] != '\0') + for(i = strlen(str)-1; i > 0 && str[i] == '/'; i--) + str[i] = '\0'; + if(i == 0 || !(str = strrchr(argv[1], '/'))) + str = argv[1]; + else + str++; + + if(argc > 2 && strlen(str) > strlen(argv[2])) { + n = strlen(str) - strlen(argv[2]); + if(!strcmp(&str[n], argv[2])) + str[n] = '\0'; + } + puts(str); + return EXIT_SUCCESS; +} diff --git a/cat.1 b/cat.1 new file mode 100644 index 0000000..08a3027 --- /dev/null +++ b/cat.1 @@ -0,0 +1,10 @@ +.TH CAT 1 sbase\-VERSION +.SH NAME +cat \- concatenate files +.SH SYNOPSIS +.B cat +.RI [ files ...] +.SH DESCRIPTION +.B cat +reads each file in sequence and writes it to stdout. If no file is given, cat +reads from stdin. diff --git a/cat.c b/cat.c new file mode 100644 index 0000000..014b173 --- /dev/null +++ b/cat.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include "util.h" + +static void cat(FILE *, const char *); + +int +main(int argc, char *argv[]) +{ + int i; + FILE *fp; + + if(argc == 1) + cat(stdin, ""); + else for(i = 1; i < argc; i++) { + if(!(fp = fopen(argv[i], "r"))) + eprintf("fopen %s:", argv[i]); + cat(fp, argv[i]); + fclose(fp); + } + return EXIT_SUCCESS; +} + +void +cat(FILE *fp, const char *str) +{ + char buf[BUFSIZ]; + size_t n; + + while((n = fread(buf, 1, sizeof buf, fp)) > 0) + if(fwrite(buf, 1, n, stdout) != n) + eprintf(": write error:"); + if(ferror(fp)) + eprintf("%s: read error:", str); +} diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..d1c4d57 --- /dev/null +++ b/config.mk @@ -0,0 +1,9 @@ +# sbase version +VERSION = 0.0 + +CC = cc +#CC = musl-gcc + +CPPFLAGS = -D_BSD_SOURCE +CFLAGS = -Os -ansi -Wall -pedantic $(CPPFLAGS) +LDFLAGS = -s -static diff --git a/echo.1 b/echo.1 new file mode 100644 index 0000000..cbe4762 --- /dev/null +++ b/echo.1 @@ -0,0 +1,14 @@ +.TH ECHO 1 sbase\-VERSION +.SH NAME +echo \- print arguments +.SH SYNOPSIS +.B echo +.RB [ \-n ] +.RI [ string ...] +.SH DESCRIPTION +.B echo +prints its arguments to stdout, separated by spaces and terminated by a newline. +.SH OPTIONS +.TP +.B \-n +Do not print terminating newline. diff --git a/echo.c b/echo.c new file mode 100644 index 0000000..3d3db19 --- /dev/null +++ b/echo.c @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + bool nflag = false; + int i; + + if(argc > 1 && !strcmp(argv[1], "-n")) + nflag = true; + for(i = nflag ? 2 : 1; i < argc; i++) { + fputs(argv[i], stdout); + if(i+1 < argc) + fputc(' ', stdout); + } + if(!nflag) + fputc('\n', stdout); + return EXIT_SUCCESS; +} diff --git a/false.1 b/false.1 new file mode 100644 index 0000000..023d702 --- /dev/null +++ b/false.1 @@ -0,0 +1,8 @@ +.TH FALSE 1 sbase\-VERSION +.SH NAME +false \- return failure +.SH SYNOPSIS +.B false +.SH DESCRIPTION +.B false +returns with a status code indicating failure. diff --git a/false.c b/false.c new file mode 100644 index 0000000..b38b1b4 --- /dev/null +++ b/false.c @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ +#include + +int +main(void) +{ + return EXIT_FAILURE; +} diff --git a/grep.1 b/grep.1 new file mode 100644 index 0000000..abeb28e --- /dev/null +++ b/grep.1 @@ -0,0 +1,43 @@ +.TH GREP 1 sbase\-VERSION +.SH NAME +grep \- search files for a pattern +.SH SYNOPSIS +.B grep +.RB [ \-c ] +.RB [ \-i ] +.RB [ \-l ] +.RB [ \-n ] +.RB [ \-q ] +.RB [ \-v ] +.I pattern +.RI [ file ...] +.SH DESCRIPTION +.B grep +searches the input files for lines that match the pattern, a regular expression as defined in +.BR regex (7). +By default each matching line is printed to stdout. If no file is given, grep +reads from stdin. +.P +The status code is 0 if any lines match, and 1 if not. If an error occurred the +status code is 2. +.SH OPTIONS +.TP +.B \-c +prints only a count of matching lines. +.TP +.B \-i +matches lines case insensitively. +.TP +.B \-l +prints only the names of files with matching lines. +.TP +.B \-n +prefixes each matching line with its line number in the input. +.TP +.B \-q +prints nothing, only returns status. +.TP +.B \-v +selects lines which do +.B not +match the pattern. diff --git a/grep.c b/grep.c new file mode 100644 index 0000000..873dd97 --- /dev/null +++ b/grep.c @@ -0,0 +1,97 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include + +static void grep(FILE *, const char *, regex_t *); + +static bool iflag = false; +static bool vflag = false; +static bool many; +static bool match = false; +static char mode = 0; + +int +main(int argc, char *argv[]) +{ + int i, flags = 0; + regex_t preg; + FILE *fp; + + for(i = 1; i < argc; i++) + if(!strcmp(argv[i], "-c")) + mode = 'c'; + else if(!strcmp(argv[i], "-i")) + iflag = true; + else if(!strcmp(argv[i], "-l")) + mode = 'l'; + else if(!strcmp(argv[i], "-n")) + mode = 'n'; + else if(!strcmp(argv[i], "-q")) + mode = 'q'; + else if(!strcmp(argv[i], "-v")) + vflag = true; + else + break; + + if(i == argc) { + fprintf(stderr, "usage: %s [-c] [-i] [-l] [-n] [-v] pattern [files...]\n", argv[0]); + exit(2); + } + if(mode == 'c') + flags |= REG_NOSUB; + if(iflag) + flags |= REG_ICASE; + regcomp(&preg, argv[i++], flags); + + many = (argc > i+1); + if(i == argc) + grep(stdin, "", &preg); + else for(; i < argc; i++) { + if(!(fp = fopen(argv[i], "r"))) { + fprintf(stderr, "fopen %s: ", argv[i]); + perror(NULL); + exit(2); + } + grep(fp, argv[i], &preg); + fclose(fp); + } + return match ? 0 : 1; +} + +void +grep(FILE *fp, const char *str, regex_t *preg) +{ + char buf[BUFSIZ]; + int n, c = 0; + + for(n = 1; fgets(buf, sizeof buf, fp); n++) { + if(regexec(preg, buf, 0, NULL, 0) ^ vflag) + continue; + if(mode == 'c') + c++; + else if(mode == 'l') { + puts(str); + break; + } + else if(mode == 'q') + exit(0); + else { + if(many) + printf("%s:", str); + if(mode == 'n') + printf("%d:", n); + fputs(buf, stdout); + } + match = true; + } + if(mode == 'c') + printf("%d\n", c); + if(ferror(fp)) { + fprintf(stderr, "%s: read error: ", str); + perror(NULL); + exit(2); + } +} diff --git a/tee.1 b/tee.1 new file mode 100644 index 0000000..36a1570 --- /dev/null +++ b/tee.1 @@ -0,0 +1,14 @@ +.TH TEE 1 sbase\-VERSION +.SH NAME +tee \- duplicate stdin +.SH SYNOPSIS +.B tee +.RB [ \-a ] +.RI [ file ...] +.SH DESCRIPTION +.B tee +writes from stdin to stdout, making copies in each file. +.SH OPTIONS +.TP +.B \-a +append to each file rather than overwriting. diff --git a/tee.c b/tee.c new file mode 100644 index 0000000..ca09a3b --- /dev/null +++ b/tee.c @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include "util.h" + +int +main(int argc, char *argv[]) +{ + bool aflag = false; + char buf[BUFSIZ]; + int i, nfps = 1; + size_t n; + FILE **fps; + + if(argc > 1 && !strcmp(argv[1], "-a")) + aflag = true; + if(!(fps = malloc(sizeof *fps))) + eprintf("malloc:"); + fps[nfps-1] = stdout; + + for(i = aflag ? 2 : 1; i < argc; i++) { + if(!(fps = realloc(fps, ++nfps * sizeof *fps))) + eprintf("realloc:"); + if(!(fps[nfps-1] = fopen(argv[i], aflag ? "a" : "w"))) + eprintf("fopen %s:", argv[i]); + } + while((n = fread(buf, 1, sizeof buf, stdin)) > 0) + for(i = 0; i < nfps; i++) + if(fwrite(buf, 1, n, fps[i]) != n) + eprintf("%s: write error:", buf); + if(ferror(stdin)) + eprintf(": read error:"); + return EXIT_SUCCESS; +} diff --git a/touch.1 b/touch.1 new file mode 100644 index 0000000..33daba0 --- /dev/null +++ b/touch.1 @@ -0,0 +1,22 @@ +.TH TOUCH 1 sbase\-VERSION +.SH NAME +touch \- set files' modification time +.SH SYNOPSIS +.B touch +.RB [ \-c ] +.RB [ \-t +.IR time ] +.RI [ file ...] +.SH DESCRIPTION +.B touch +sets the files' modification time to the current time. If a file does not exist +it is created. +.SH OPTIONS +.TP +.B \-c +do not create files if they do not exist. +.TP +.BI \-t " time" +sets the files' modification time to +.IR time , +given as the number of seconds since the Unix epoch. diff --git a/touch.c b/touch.c new file mode 100644 index 0000000..c0e3599 --- /dev/null +++ b/touch.c @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.h" + +static void touch(const char *); + +static bool cflag = false; +static time_t t; + +int +main(int argc, char *argv[]) +{ + int i; + + t = time(NULL); + + for(i = 1; i < argc; i++) + if(!strcmp(argv[i], "-c")) + cflag = true; + else if(!strcmp(argv[i], "-t") && i+1 < argc) + t = strtol(argv[++i], NULL, 0); + else + break; + + for(; i < argc; i++) + touch(argv[i]); + return EXIT_SUCCESS; +} + +void +touch(const char *str) +{ + int fd; + struct stat st; + struct utimbuf ut; + + if(stat(str, &st) < 0) { + if(errno != ENOENT) + eprintf("stat %s:", str); + if(cflag) + return; + if((fd = creat(str, O_RDONLY|S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) < 0) + eprintf("creat %s:", str); + close(fd); + } + ut.actime = st.st_atime; + ut.modtime = t; + if(utime(str, &ut) < 0) + eprintf("utime %s:", str); +} diff --git a/true.1 b/true.1 new file mode 100644 index 0000000..ed947be --- /dev/null +++ b/true.1 @@ -0,0 +1,8 @@ +.TH TRUE 1 sbase\-VERSION +.SH NAME +true \- return success +.SH SYNOPSIS +.B true +.SH DESCRIPTION +.B true +returns with a status code indicating success. diff --git a/true.c b/true.c new file mode 100644 index 0000000..8737dd5 --- /dev/null +++ b/true.c @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ +#include + +int +main(void) +{ + return EXIT_SUCCESS; +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..eb3006d --- /dev/null +++ b/util.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include "util.h" + +void +eprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if(fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } + exit(EXIT_FAILURE); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..9966622 --- /dev/null +++ b/util.h @@ -0,0 +1,3 @@ +/* See LICENSE file for copyright and license details. */ + +void eprintf(const char *, ...); diff --git a/wc.1 b/wc.1 new file mode 100644 index 0000000..1f85998 --- /dev/null +++ b/wc.1 @@ -0,0 +1,28 @@ +.TH WC 1 sbase\-VERSION +.SH NAME +wc \- word count +.SH SYNOPSIS +.B wc +.RB [ \-c ] +.RB [ \-l ] +.RB [ \-m ] +.RB [ \-w ] +.RI [ file ...] +.SH DESCRIPTION +.B wc +prints the number of lines, words, and bytes in each file. If any flags are +given, wc will print only the requested information. If no files are given, wc +reads stdin. +.SH OPTIONS +.TP +.B \-c +print the number of bytes. +.TP +.B \-l +print the number of lines. +.TP +.B \-m +print the number of characters, not bytes. +.TP +.B \-w +print the number of words. diff --git a/wc.c b/wc.c new file mode 100644 index 0000000..a137d96 --- /dev/null +++ b/wc.c @@ -0,0 +1,89 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include +#include +#include "util.h" + +static void output(const char *, long, long, long); +static void wc(FILE *, const char *); + +static bool lflag = false; +static bool wflag = false; +static char cmode = 0; +static long tc = 0, tl = 0, tw = 0; + +int +main(int argc, char *argv[]) +{ + bool many; + int i; + FILE *fp; + + for(i = 1; i < argc; i++) + if(!strcmp(argv[i], "-c")) + cmode = 'c'; + else if(!strcmp(argv[i], "-l")) + lflag = true; + else if(!strcmp(argv[i], "-m")) + cmode = 'm'; + else if(!strcmp(argv[i], "-w")) + wflag = true; + else + break; + many = (argc > i+1); + + if(i == argc) + wc(stdin, NULL); + else for(; i < argc; i++) { + if(!(fp = fopen(argv[i], "r"))) + eprintf("fopen %s:", argv[i]); + wc(fp, argv[i]); + fclose(fp); + } + if(many) + output("total", tc, tl, tw); + return EXIT_SUCCESS; +} + +void +output(const char *str, long nc, long nl, long nw) +{ + bool noflags = !cmode && !lflag && !wflag; + + if(lflag || noflags) + printf(" %5ld", nl); + if(wflag || noflags) + printf(" %5ld", nw); + if(cmode || noflags) + printf(" %5ld", nc); + if(str) + printf(" %s", str); + fputc('\n', stdout); +} + +void +wc(FILE *fp, const char *str) +{ + bool word = false; + char c; + long nc = 0, nl = 0, nw = 0; + + while((c = fgetc(fp)) != EOF) { + if(cmode != 'm' || (c & 0xc0) != 0x80) + nc++; + if(c == '\n') + nl++; + if(!isspace(c)) + word = true; + else if(word) { + word = false; + nw++; + } + } + tc += nc; + tl += nl; + tw += nw; + output(str, nc, nl, nw); +}