├── .gitignore ├── Makefile └── envsubst.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | envsubst -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PACKAGE_NAME := envsubst 2 | PACKAGE_VERSION := 0.1 3 | 4 | prefix ?= /usr/local 5 | bindir ?= /bin 6 | 7 | CFLAGS ?= -Os -fomit-frame-pointer 8 | CC ?= ${CROSS_COMPILE}cc 9 | INSTALL ?= install 10 | 11 | ENVSUBST_SRC = envsubst.c 12 | ENVSUBST_OBJ = ${ENVSUBST_SRC:.c=.o} 13 | 14 | CFLAGS += -DENVSUBST_VERSION='"${PACKAGE_VERSION}"' 15 | 16 | envsubst: ${ENVSUBST_OBJ} 17 | ${CC} -o $@ ${ENVSUBST_OBJ} 18 | 19 | clean: 20 | ${RM} ${ENVSUBST_OBJ} envsubst 21 | 22 | install: 23 | ${INSTALL} -D -m755 envsubst ${DESTDIR}${prefix}${bindir}/envsubst 24 | 25 | DIST_NAME = ${PACKAGE_NAME}-${PACKAGE_VERSION} 26 | DIST_TARBALL = ${DIST_NAME}.tar.xz 27 | 28 | check: 29 | distcheck: check dist 30 | dist: ${DIST_TARBALL} 31 | ${DIST_TARBALL}: 32 | git archive --format=tar --prefix=${DIST_NAME}/ -o ${DIST_NAME}.tar ${DIST_NAME} 33 | xz ${DIST_NAME}.tar -------------------------------------------------------------------------------- /envsubst.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Ariadne Conill 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * This software is provided 'as is' and without any warranty, express or 9 | * implied. In no event shall the authors be liable for any damages arising 10 | * from the use of this software. 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | void 21 | usage(void) 22 | { 23 | printf("usage: envsubst [OPTIONS] [VARIABLE-NAMES...]\n"); 24 | printf(" VARIABLE-NAMES can be specified in shell format or bare.\n"); 25 | exit(EXIT_SUCCESS); 26 | } 27 | 28 | void 29 | version(void) 30 | { 31 | printf("envsubst " ENVSUBST_VERSION "\n"); 32 | exit(EXIT_SUCCESS); 33 | } 34 | 35 | static char **variables = NULL; 36 | static size_t variable_count = 0; 37 | 38 | void 39 | dump_variables(void) 40 | { 41 | if (!variable_count) 42 | { 43 | fprintf(stderr, "envsubst: no variables defined\n"); 44 | exit(EXIT_FAILURE); 45 | } 46 | 47 | for (size_t i = 0; i < variable_count; i++) 48 | { 49 | printf("%s\n", variables[i]); 50 | } 51 | } 52 | 53 | const char * 54 | normalize_variable(const char *var, bool require_sigil) 55 | { 56 | static char scratch[4096]; 57 | 58 | if (*var == '$') 59 | { 60 | var++; 61 | 62 | if (*var == '{') 63 | { 64 | var++; 65 | } 66 | } 67 | else if (require_sigil) 68 | { 69 | return NULL; 70 | } 71 | 72 | strlcpy(scratch, var, sizeof scratch); 73 | 74 | char *p = strchr(scratch, '}'); 75 | if (p != NULL) 76 | { 77 | *p = '\0'; 78 | } 79 | 80 | return scratch; 81 | } 82 | 83 | void 84 | push_variable(const char *var) 85 | { 86 | variable_count++; 87 | variables = reallocarray(variables, variable_count + 1, sizeof (void *)); 88 | 89 | if (variables == NULL) 90 | { 91 | fprintf(stderr, "envsubst: reallocarray: %s\n", strerror(errno)); 92 | exit(EXIT_FAILURE); 93 | } 94 | 95 | variables[variable_count - 1] = strdup(var); 96 | } 97 | 98 | void 99 | process_variable(const char *var) 100 | { 101 | const char *var_token = normalize_variable(var, false); 102 | 103 | if (var_token == NULL) 104 | { 105 | fprintf(stderr, "envsubst: failed to process '%s'\n", var_token); 106 | return; 107 | } 108 | 109 | push_variable(var_token); 110 | } 111 | 112 | void 113 | process_argument(char *arg) 114 | { 115 | char *token = strtok(arg, ","); 116 | 117 | for (; token != NULL; token = strtok(NULL, ",")) 118 | { 119 | process_variable(token); 120 | } 121 | } 122 | 123 | bool 124 | allow_variable(const char *token) 125 | { 126 | if (!variable_count) 127 | { 128 | return true; 129 | } 130 | 131 | for (size_t i = 0; i < variable_count; i++) 132 | { 133 | if (!strcmp(token, variables[i])) 134 | { 135 | return true; 136 | } 137 | } 138 | 139 | return false; 140 | } 141 | 142 | void 143 | free_variables(void) 144 | { 145 | for (size_t i = 0; i < variable_count; i++) 146 | { 147 | free(variables[i]); 148 | } 149 | 150 | free(variables); 151 | } 152 | 153 | void 154 | print_variable(FILE *stream, const char *token, const char *orig_token) 155 | { 156 | if (allow_variable(token)) 157 | { 158 | char *envp = getenv(token); 159 | 160 | if (envp == NULL) 161 | { 162 | return; 163 | } 164 | 165 | fprintf(stream, "%s", envp); 166 | } 167 | else 168 | { 169 | fprintf(stream, "%s", orig_token); 170 | } 171 | } 172 | 173 | void 174 | process_input(FILE *stream) 175 | { 176 | char *line = NULL; 177 | size_t len = 0; 178 | ssize_t nread; 179 | 180 | while ((nread = getline(&line, &len, stream)) != -1) 181 | { 182 | for (char *p = line; *p != '\0'; p++) 183 | { 184 | switch (*p) 185 | { 186 | case '$': { 187 | char *end_p = strpbrk(p, " /\t\r\n"); 188 | char ospace = *end_p; 189 | *end_p = '\0'; 190 | 191 | const char *token = normalize_variable(p, true); 192 | print_variable(stdout, token, p); 193 | 194 | /* we want to still process the whitespace */ 195 | *end_p = ospace; 196 | p = end_p - 1; 197 | 198 | break; 199 | } 200 | default: 201 | fputc(*p, stdout); 202 | break; 203 | } 204 | } 205 | } 206 | 207 | free(line); 208 | } 209 | 210 | int 211 | main(int argc, char *argv[]) 212 | { 213 | bool want_variables = false; 214 | const struct option opts[] = { 215 | {"help", no_argument, NULL, 'h'}, 216 | {"variables", no_argument, NULL, 'v'}, 217 | {"version", no_argument, NULL, 'V'}, 218 | {0, 0, 0, 0}, 219 | }; 220 | 221 | for (;;) 222 | { 223 | int c = getopt_long(argc, argv, "hvV", opts, NULL); 224 | 225 | if (c == -1) 226 | { 227 | break; 228 | } 229 | else if (c == 'h') 230 | { 231 | usage(); 232 | } 233 | else if (c == 'v') 234 | { 235 | want_variables = true; 236 | } 237 | else if (c == 'V') 238 | { 239 | version(); 240 | } 241 | } 242 | 243 | for (int c = optind; c < argc; c++) 244 | { 245 | process_argument(argv[c]); 246 | } 247 | 248 | if (want_variables) 249 | { 250 | dump_variables(); 251 | return EXIT_SUCCESS; 252 | } 253 | 254 | process_input(stdin); 255 | free_variables(); 256 | 257 | return EXIT_SUCCESS; 258 | } --------------------------------------------------------------------------------