sbase

suckless unix tools
git clone git://git.2f30.org/sbase.git
Log | Files | Refs | README | LICENSE

commit 8e26716a5a33fc4a7c6f5f0a72356523a8ca9119
Author: Connor Lane Smith <cls@lubutu.com>
Date:   Mon May 23 02:36:34 +0100

initial commit
Diffstat:
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(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE @@ -0,0 +1,21 @@ +MIT/X Consortium License + +© 2011 Connor Lane Smith <cls@lubutu.com> + +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 @@ -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 @@ -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 @@ -0,0 +1,30 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#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 @@ -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 @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include "util.h" + +static void cat(FILE *, const char *); + +int +main(int argc, char *argv[]) +{ + int i; + FILE *fp; + + if(argc == 1) + cat(stdin, "<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("<stdout>: write error:"); + if(ferror(fp)) + eprintf("%s: read error:", str); +} diff --git a/config.mk 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 @@ -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 @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +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 @@ -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 @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> + +int +main(void) +{ + return EXIT_FAILURE; +} diff --git a/grep.1 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 @@ -0,0 +1,97 @@ +/* See LICENSE file for copyright and license details. */ +#include <regex.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +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, "<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 @@ -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 @@ -0,0 +1,36 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#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("<stdin>: read error:"); + return EXIT_SUCCESS; +} diff --git a/touch.1 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 @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ +#include <errno.h> +#include <fcntl.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <utime.h> +#include <sys/stat.h> +#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 @@ -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 @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdlib.h> + +int +main(void) +{ + return EXIT_SUCCESS; +} diff --git a/util.c b/util.c @@ -0,0 +1,22 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#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 @@ -0,0 +1,3 @@ +/* See LICENSE file for copyright and license details. */ + +void eprintf(const char *, ...); diff --git a/wc.1 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 @@ -0,0 +1,89 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#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); +}