├── .gitignore ├── Makefile ├── README ├── envv.1 └── envv.c /.gitignore: -------------------------------------------------------------------------------- 1 | versions 2 | .svn 3 | envv 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for envv. 2 | 3 | VERSION=1.7 4 | 5 | SRCS=envv.c 6 | FILES=$(SRCS) Makefile README envv.1 7 | 8 | #Which compiler to use? 9 | CC=gcc 10 | 11 | all: envv 12 | 13 | envv: envv.c 14 | $(CC) -o envv $(CFLAGS) $(CDEFS) $(CEXTRAS) envv.c 15 | 16 | clean: 17 | rm -f *~ *.o core 18 | 19 | clobber: 20 | rm -f *~ *.o core envv 21 | 22 | targz: 23 | git archive --format=tar --prefix=envv-$(VERSION)/ HEAD | gzip -v -9 > envv-$(VERSION).tar.gz 24 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ENVV 2 | 3 | Envv is a shell-independent way of handling environment variables. 4 | It lets you set environment variables, and manipulate colon-separated 5 | paths in an easy, shell-independent way. This makes writing setup scripts 6 | for software products a snap. Instead of users hard-coding initialization 7 | steps in their .cshrc or .profile files, they can source system-wide 8 | files. Then, if you ever move any software packages, users don't 9 | have any problem. 10 | 11 | For example, suppose the commercial software packaged FOOBAR requires 12 | '/usr/local/foobar/bin' to be on the user's path, and requires the 13 | environment variable 'FOOHOME' to be set to '/usr/local/foobar' 14 | 15 | Just create the following script: 16 | 17 | eval `envv add PATH /usr/local/foobar/bin` 18 | eval `envv set FOOHOME /usr/local/foobar` 19 | 20 | Then, users of csh, tcsh, bash, sh or ksh can simply source the script, 21 | and everything will work fine. 22 | 23 | BUILDING ENVV 24 | 25 | Envv is a ridiculously simple program. You may want to glance at the 26 | first few lines of envv.c, and take a look at the Makefile. But all you 27 | should have to to do compile it is type 'make'. This makes the executable 28 | 'envv', which you can copy to your favourite system directory. The manual 29 | page is in 'envv.1' 30 | 31 | CHANGES TO ENVV: 32 | 33 | * Version 1.7 (14 July 2011) 34 | 35 | + Licence change 36 | 37 | ENVV is now licensed under the MIT license 38 | (http://en.wikipedia.org/wiki/MIT_License) 39 | 40 | The official license terms are in the man page, envv.1 41 | 42 | + Bug fixes 43 | 44 | - Fixed segmentation fault identified at 45 | https://github.com/jakewendt/envv/issues/1 46 | 47 | * Version 1.6 (11 July 1995) 48 | 49 | + Bug fixes 50 | 51 | - Fixed the path comparison function. The old version would 52 | incorrectly match /u/share and /usr/foo because the trailing-slash 53 | logic was confused. Thanks to Caleb Epstein for finding and reporting 54 | the bug. 55 | 56 | * Version 1.5 57 | 58 | + Bug fixes 59 | 60 | - Made directives case-insensitive (suggested by Steve Shoopak.) 61 | 62 | - Changed the definition of MAX_VAL_LEN from 256 to 2048 to allow for 63 | very long paths. 64 | 65 | * Version 1.4 (10 May 1994) 66 | 67 | + Bug fixes 68 | 69 | - If a set command was followed by add/move/del commands on stdin, 70 | it didn't work -- the results of the set were not saved in envv's 71 | environment. 72 | 73 | * Version 1.3 (10 May 1994) 74 | 75 | + Bug fixes 76 | 77 | - I was freeing a string after sending it to putenv(). Bad news. 78 | This patch fixes that. 79 | 80 | * Version 1.2 (18 April 1994) 81 | 82 | + Enhancements 83 | 84 | - Added the "local" directive to set variables without exporting them 85 | to the environment. 86 | 87 | - Added the '-e', -h' and '-s' options. 88 | 89 | - If no directives are supplied on command line, envv reads directives 90 | from stdin and issues commands based on those directives. Directives 91 | on stdin should be supplied one-per-line. 92 | 93 | + Bug fixes 94 | 95 | - Fixed up the usage display to show the "choose" directive, which had 96 | been missing. 97 | 98 | * Version 1.1 (22 March 1994) 99 | 100 | + Enhancements 101 | 102 | - Modified the path manipulation commands so that trailing slashes do 103 | not affect comparison of directories. That is, foo/bar/ and foo/bar 104 | are considered the same. 105 | 106 | - Added the 'zsh' shell to the list of shells envv recognizes. 107 | 108 | - Added the 'choose' directive -- see the man page. 109 | 110 | * Version 1.0 (8 Feb 1994) 111 | 112 | - First version. 113 | 114 | -- 115 | David F. Skoll 116 | Roaring Penguin Software Inc. 117 | 118 | 613 231-6599 119 | -------------------------------------------------------------------------------- /envv.1: -------------------------------------------------------------------------------- 1 | .TH ENVV 1 "18 April 1994" 2 | .UC 4 3 | .SH NAME 4 | envv \- manipulate environment variables in shell-independent manner 5 | .SH SYNOPSIS 6 | \fBenvv \fR[\fIoptions\fR]\fB set\fR \fIenv_var\fR \fIvalue\fR 7 | .PP 8 | \fBenvv \fR[\fIoptions\fR]\fB local\fR \fIenv_var\fR \fIvalue\fR 9 | .PP 10 | \fBenvv \fR[\fIoptions\fR]\fB add\fR \fIpath_var\fR \fIdir\fR [\fIpos\fR] 11 | .PP 12 | \fBenvv \fR[\fIoptions\fR]\fB move\fR \fIpath_var\fR \fIdir\fR \fIpos\fR 13 | .PP 14 | \fBenvv \fR[\fIoptions\fR]\fB del\fR \fIpath_var\fR \fIdir\fR 15 | .PP 16 | \fBenvv \fR[\fIoptions\fR]\fB choose\fR \fIsh_val\fR \fIcsh_val\fR 17 | .PP 18 | \fBenvv \fR[\fIoptions\fR] 19 | .SH OPTIONS 20 | .TP 21 | .B \-e 22 | Do not escape shell meta-characters when issuing commands 23 | .TP 24 | .B \-s 25 | Issue a semicolon after each command 26 | .TP 27 | .B \-h 28 | Display usage information 29 | .SH DESCRIPTION 30 | \fBEnvv\fR is used to manipulate environment variables in a shell-independent 31 | manner. It is most useful in administrator-maintained setup files for 32 | various software packages. 33 | .PP 34 | \fBEnvv\fR accepts directives, either on the command line or from 35 | standard input, and issues shell commands tailored to the user's 36 | shell. 37 | .PP 38 | For example, on our system, each software package has a setup script 39 | in a fixed directory (/usr/share/setup) which sets up environment 40 | variables and adds directories to the users' PATH as required by the 41 | package. The scripts to do this are slightly different for csh-like 42 | shells (csh, tcsh) and sh-like shells (sh, ksh, bash.) \fBEnvv\fR 43 | eliminates the need for two separate scripts. 44 | .PP 45 | \fBEnvv\fR examines the \fBSHELL\fR environment variable for the name 46 | of a shell which it recognizes. If that doesn't work, it uses information 47 | from the \fBpasswd\fR file to find the user's shell. It then issues 48 | commands appropriate for the shell to standard output. Here's a simple 49 | example: 50 | .PP 51 | .nf 52 | eval `envv set NAME David` 53 | .fi 54 | .PP 55 | If your shell is csh, then \fBenvv\fR emits "setenv NAME David" to 56 | standard output. If your shell is sh, the it emits 57 | "NAME=David; export NAME" to standard output. Either way, the environment 58 | variable is set correctly. 59 | .SH SET 60 | The simplest directive is the \fBset\fR command, illustrated above. 61 | Note that shell characters are normally escaped, so that: 62 | .PP 63 | .nf 64 | envv set NAME 'David* F. Skoll' 65 | .fi 66 | .PP 67 | will emit "setenv NAME David\\*\\ F.\\ Skoll" under csh. However, if 68 | the \fB\-e\fR option is supplied, then \fBEnvv\fR will emit "setenv 69 | NAME David* F. Skoll", with all the difficulties that may imply. 70 | .SH LOCAL 71 | \fBLocal\fR is similar to \fBset\fR, except that it does not export 72 | the variable to the environment. Thus: 73 | .PP 74 | .nf 75 | envv local name David 76 | .fi 77 | .PP 78 | will emit "set name=David" in csh, and "name=David" in sh or bash. 79 | .SH ADD 80 | The \fBadd\fR directive adds the specified directory to a colon-separated 81 | path list, if the directory is not already present. If \fBpos\fR is 82 | supplied, the new directory is placed at the specified position, which 83 | are numbered starting with 1. If \fBpos\fR is not specified, or is 84 | out-of-range, the new directory is added at the end. 85 | .PP 86 | For example, suppose that under csh, your PATH is /bin:/usr/bin. Then: 87 | .PP 88 | .nf 89 | envv add PATH /foo yields "setenv PATH /bin:/usr/bin:/foo" 90 | envv add PATH /foo 1 yields "setenv PATH /foo:/bin:/usr/bin" 91 | envv add PATH /foo 2 yields "setenv PATH /bin:/foo:/usr/bin" 92 | envv add PATH /bin yields nothing - /bin already on path 93 | envv add PATH /bin 2 yields "setenv PATH /usr/bin:/bin" 94 | .fi 95 | .PP 96 | The last example shows that an 'add' is converted to a 'move' if the 97 | directory is already on the path, and a position is supplied. Of course, 98 | to have any effect, all of the above examples would be used in an 99 | eval `...` structure. 100 | .SH DEL 101 | The \fBdel\fR command deletes a directory from the path variable. 102 | Suppose that MANPATH is set to /man:/usr/man, and that your shell is 103 | bash. Then: 104 | .PP 105 | .nf 106 | envv del MANPATH /man yields "MANPATH=/usr/man; export MANPATH" 107 | envv del MANPATH /usr/man yields "MANPATH=/man; export MANPATH" 108 | envv del MANPATH /foo yields nothing 109 | .fi 110 | .SH MOVE 111 | The \fBmove\fR command moves a directory in the path to the specified 112 | position. Suppose you're running tcsh, and the environment variable 113 | P is set to a:b:c:d. Then: 114 | .PP 115 | .nf 116 | envv move P a 2 yields "setenv P b:a:c:d" 117 | envv move P a 999 yields "setenv P b:c:d:a" 118 | envv move P d 1 yields "setenv P d:a:b:c" 119 | envv move P e 1 yields nothing - e is not on path. 120 | .fi 121 | .SH CHOOSE 122 | The \fBchoose\fR command is very simple: It takes two arguments. If 123 | the user's shell is like \fBsh\fR, then the first argument is printed. 124 | If it is like \fBcsh\fR, then the second argument is printed. 125 | .PP 126 | This is useful if commercial vendors have supplied two versions of 127 | scripts which must be sourced. Your system script can select the 128 | proper script as follows: 129 | .PP 130 | .nf 131 | source `envv choose script.sh script.csh` 132 | .fi 133 | .PP 134 | This can also be used as an escape hatch for complicated tests which 135 | really do require two separate shell scripts. 136 | .PP 137 | .SH SUPPLYING DIRECTIVES ON STANDARD INPUT 138 | If no directives are given on the command line, then \fBenvv\fR 139 | reads standard input for directives, which should be supplied 140 | one-per-line. \fBEnvv\fR processes all of the directives until 141 | end-of-file is encountered. For example, you could do something 142 | like this: 143 | .PP 144 | .nf 145 | envv << 'END_OF_STUFF' > /tmp/env.$$ 146 | set A 1 147 | set B 2 148 | add PATH /bin/foo 149 | 'END_OF_STUFF' 150 | source /tmp/env.$$ 151 | rm /tmp/env.$$ 152 | .fi 153 | .PP 154 | This creates a temporary shell script which holds the commands to 155 | set A and B, and add /bin/foo to the path. This temporary script 156 | is then sourced and deleted. If you have many variables to 157 | set, this may be faster and/or easier to maintain than multiple 158 | \fBenvv\fR commands. 159 | .PP 160 | When \fBenvv\fR reads from standard input, it uses whitespace characters 161 | to separate directives from their arguments, and the arguments from each 162 | other. If you want to include whitespace in an argument, escape it with 163 | a backslash. No other characters need be escaped. For example, if you 164 | want to set the environment variable NAME to "David Skoll", use this in 165 | the input to \fBenvv\fR: 166 | .PP 167 | .nf 168 | set NAME David\\ Skoll 169 | .fi 170 | .PP 171 | .SH NOTES 172 | The path-manipulation directives (\fBadd\fR, \fBmove\fR, \fBdel\fR) 173 | ignore trailing slashes when comparing path components. Thus, 174 | "/usr/local" and "/usr/local/" are considered the same. When 175 | components are added or moved in a path variable, they inherit 176 | whatever slashes are supplied in the \fIdir\fR argument. Experiment... 177 | .PP 178 | The path-manipulation commands can add, move or delete only one 179 | directory at a time. For example, use this: 180 | .PP 181 | .nf 182 | add PATH /foo 183 | add PATH /bar 184 | .fi 185 | .PP 186 | and not this: 187 | .PP 188 | .nf 189 | add PATH /foo:/bar 190 | .fi 191 | .PP 192 | .SH AUTHOR 193 | \fBEnvv\fR is written by David F. Skoll. 194 | .SH LICENSE 195 | \fBEnvv\fR may be distributed according to the following license (the 196 | "MIT X/Consortium" license: 197 | 198 | Copyright (C) 1994-2011 by Roaring Penguin Software Inc. 199 | 200 | Permission is hereby granted, free of charge, to any person obtaining a copy 201 | of this software and associated documentation files (the "Software"), to deal 202 | in the Software without restriction, including without limitation the rights 203 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 204 | copies of the Software, and to permit persons to whom the Software is 205 | furnished to do so, subject to the following conditions: 206 | 207 | The above copyright notice and this permission notice shall be included in 208 | all copies or substantial portions of the Software. 209 | 210 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 211 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 212 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 213 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 214 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 215 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 216 | THE SOFTWARE. 217 | 218 | .SH BUGS 219 | If you have multiple \fBadd\fR, \fBdel\fR or \fBmove\fR commands in a 220 | standard-input command list, they emit multiple shell commands to set 221 | the path variable. It would be nicer if only one command which 222 | reflected the final result were emitted. 223 | -------------------------------------------------------------------------------- /envv.c: -------------------------------------------------------------------------------- 1 | /***************************************************************/ 2 | /* */ 3 | /* ENVV.C */ 4 | /* */ 5 | /* Program to manipulate environment variables in a */ 6 | /* shell-independent way. */ 7 | /* */ 8 | /* Copyright (C) 1994-2011 by Roaring Penguin Software Inc. */ 9 | /* http://www.roaringpenguin.com */ 10 | /* dfs@roaringpenguin.com */ 11 | /* */ 12 | /* Usage: */ 13 | /* eval `envv choose sh-name csh-name` */ 14 | /* eval `envv set ENVVAR value` */ 15 | /* eval `envv local VAR value` */ 16 | /* eval `envv add PATHVAR dir [position]` */ 17 | /* eval `envv del PATHVAR dir` */ 18 | /* eval `envv move PATHVAR dir position` */ 19 | /* */ 20 | /* Options: */ 21 | /* -e = don't escape shell chars */ 22 | /* -s = put trailing semicolon after each command. */ 23 | /* -h = display usage information */ 24 | /* */ 25 | /* If no commands given on command line, read from stdin */ 26 | /* */ 27 | /***************************************************************/ 28 | #define VERSION "1.7" 29 | #define _POSIX_C_SOURCE 200809L 30 | 31 | /* For strdup prototype */ 32 | #define _SVID_SOURCE 1 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | /* Maximum number of components allowed in a colon-separated path list */ 46 | 47 | #define MAXCOMPONENTS 256 48 | 49 | char *PathComp[MAXCOMPONENTS]; 50 | int NumComponents; 51 | 52 | /* Possible types of shells */ 53 | #define NO_SH -1 54 | #define SH_LIKE 0 55 | #define CSH_LIKE 1 56 | 57 | /* Possible directives */ 58 | #define NO_D -1 59 | #define D_SET 0 60 | #define D_MOVE 1 61 | #define D_ADD 2 62 | #define D_DEL 3 63 | #define D_CHOOSE 4 64 | #define D_LOCAL 5 65 | 66 | /* Positions */ 67 | #define NO_P 0 68 | 69 | typedef struct { 70 | char *name; 71 | int type; 72 | } ShellType; 73 | 74 | ShellType Shells[] = { 75 | { "ash", SH_LIKE }, 76 | { "bash", SH_LIKE }, 77 | { "csh", CSH_LIKE }, 78 | { "dash", SH_LIKE }, 79 | { "ksh", SH_LIKE }, 80 | { "mksh", SH_LIKE }, 81 | { "rsh", SH_LIKE }, 82 | { "sh", SH_LIKE }, 83 | { "tcsh", CSH_LIKE }, 84 | { "zsh", SH_LIKE }, 85 | { NULL, NO_SH } 86 | }; 87 | 88 | /* A list of all the characters which should be escaped */ 89 | char *escape = "\\\"'!$%^&*()[]<>{}`~| ;?\t"; 90 | static int ShouldEscape = 1; 91 | 92 | /* Trailing semicolon? */ 93 | char *TrailingSemi = "\n"; 94 | 95 | /* Was command supplied on cmd. line? */ 96 | static int UseCmdLine; 97 | 98 | /* Command-line arguments */ 99 | int Argc; 100 | char **Argv; 101 | int FirstArg; 102 | 103 | /* Global vars for directives, args, etc. */ 104 | #define MAX_DIR_LEN 40 105 | #define MAX_VAR_LEN 256 106 | #define MAX_VAL_LEN 2048 107 | #define MAX_POS_LEN 20 108 | 109 | char Directive[MAX_DIR_LEN+1]; 110 | char Var[MAX_VAR_LEN+1]; 111 | char Val[MAX_VAL_LEN+1]; 112 | char Pos[MAX_POS_LEN+1]; 113 | int ArgsSupplied; 114 | 115 | /* Function Prototypes */ 116 | void Init (int argc, char *argv[]); 117 | int FigureShellTypeFromName (char *s); 118 | int GetShellType (void); 119 | void DoSetenv (const char *var, const char *val, int shell, int local); 120 | void DoAdd (const char *var, const char *val, int shell, int pos); 121 | void DoDel (const char *var, const char *val, int shell); 122 | void DoMove (const char *var, const char *val, int shell, int pos); 123 | void DoChoose (const char *val1, const char *val2, int shell); 124 | int SplitPath (char *path); 125 | int FindCurPos (const char *dir); 126 | void PrintEscaped (const char *s, int colon); 127 | void PathManip (const char *var, const char *dir, int shell, int pos, int what); 128 | void Usage (const char *name); 129 | int ComparePathElements (const char *p1, const char *p2); 130 | int GetCommand (void); 131 | int ReadEscapedToken (char *buf, int len, int eoln_flag); 132 | int ReadCmdFromStdin (void); 133 | 134 | /***************************************************************/ 135 | /* */ 136 | /* PrintEscaped */ 137 | /* */ 138 | /* Print a string, escaping shell chars. A baroque set of */ 139 | /* params: If colon is 0, reset internal flag. If it's 1, */ 140 | /* and internal flag is reset, then set internal flag. If it */ 141 | /* is 1 and internal flag is set, print a colon before string */ 142 | /* */ 143 | /***************************************************************/ 144 | void PrintEscaped(const char *s, int colon) 145 | { 146 | static int internal_flag = 0; 147 | 148 | if (!colon) internal_flag = 0; 149 | 150 | if (!s || !*s) return; 151 | 152 | if (colon) { 153 | if (internal_flag) putchar(':'); 154 | else internal_flag = 1; 155 | } 156 | while (*s) { 157 | if (ShouldEscape && strchr(escape, *s)) putchar('\\'); 158 | putchar(*s); 159 | s++; 160 | } 161 | } 162 | 163 | /***************************************************************/ 164 | /* */ 165 | /* GetShellType */ 166 | /* */ 167 | /* Get the type of the user's shell. First, look for SHELL */ 168 | /* environment variable. If that doesn't work, use getpwuid */ 169 | /* */ 170 | /***************************************************************/ 171 | int GetShellType(void) 172 | { 173 | char *s; 174 | int type; 175 | struct passwd *pw; 176 | 177 | s = getenv("SHELL"); 178 | 179 | type = FigureShellTypeFromName(s); 180 | if (type != NO_SH) return type; 181 | 182 | /* Didn't work -- use getpwuid */ 183 | pw = getpwuid(geteuid()); 184 | if (pw) 185 | type = FigureShellTypeFromName(pw->pw_shell); 186 | 187 | return type; 188 | } 189 | 190 | /***************************************************************/ 191 | /* */ 192 | /* FigureShellTypeFromName */ 193 | /* */ 194 | /* Given the name of a shell, figure out if it's like sh or */ 195 | /* csh. */ 196 | /* */ 197 | /***************************************************************/ 198 | int FigureShellTypeFromName(char *s) 199 | { 200 | int i; 201 | char *t; 202 | 203 | if (!s) return NO_SH; 204 | 205 | /* Move past the last '/' in the shell name */ 206 | for (t=s; *s; s++) 207 | if (*s == '/') t=s+1; 208 | 209 | /* Figure out the type of shell */ 210 | for (i=0; Shells[i].name; i++) 211 | if (!strcmp(t, Shells[i].name)) return Shells[i].type; 212 | 213 | /* Didn't match anything */ 214 | return NO_SH; 215 | } 216 | 217 | /***************************************************************/ 218 | /* */ 219 | /* ComparePathElements */ 220 | /* */ 221 | /* Return 0 if two path elements are the same, non-zero */ 222 | /* otherwise. Trailing slashes do not participate in the */ 223 | /* comparison. */ 224 | /* */ 225 | /***************************************************************/ 226 | int ComparePathElements(const char *p1, const char *p2) 227 | { 228 | while (*p1) { 229 | if (*p1 != *p2) break; 230 | p1++; 231 | p2++; 232 | } 233 | 234 | /* Trailing slashes don't count */ 235 | if ((*p1 != '/' && *p1 != 0) || 236 | (*p2 != '/' && *p2 != 0)) { 237 | return (*p1 - *p2); 238 | } 239 | 240 | while(*p1 == '/') p1++; 241 | while(*p2 == '/') p2++; 242 | return (*p1 - *p2); 243 | 244 | } 245 | 246 | /***************************************************************/ 247 | /***************************************************************/ 248 | /***************************************************************/ 249 | /*** ***/ 250 | /*** MAIN PROGRAM ***/ 251 | /*** ***/ 252 | /***************************************************************/ 253 | /***************************************************************/ 254 | /***************************************************************/ 255 | int main(int argc, char *argv[]) 256 | { 257 | int what; 258 | int pos; 259 | int shell; 260 | 261 | Init(argc, argv); 262 | 263 | shell = GetShellType(); 264 | if (shell == NO_SH) { 265 | fprintf(stderr, "%s: Can't figure out shell type!\n", argv[0]); 266 | return 1; 267 | } 268 | 269 | while(1) { 270 | what = NO_D; 271 | pos = NO_P; 272 | if (!GetCommand()) return 0; 273 | if (ArgsSupplied < 3) { 274 | if (UseCmdLine) { 275 | Usage(Argv[0]); 276 | return 1; 277 | } else { 278 | fprintf(stderr, "%s: not enough arguments in command\n", Argv[0]); 279 | } 280 | continue; 281 | } 282 | if (!strcasecmp(Directive, "set")) what = D_SET; 283 | else if (!strcasecmp(Directive, "add")) what = D_ADD; 284 | else if (!strcasecmp(Directive, "del")) what = D_DEL; 285 | else if (!strcasecmp(Directive, "move")) what = D_MOVE; 286 | else if (!strcasecmp(Directive, "choose")) what = D_CHOOSE; 287 | else if (!strcasecmp(Directive, "local")) what = D_LOCAL; 288 | 289 | if (what == NO_D) { 290 | if (UseCmdLine) { 291 | Usage(argv[0]); 292 | return 1; 293 | } else { 294 | fprintf(stderr, "%s: unknown directive %s\n", Argv[0], Directive); 295 | continue; 296 | } 297 | } 298 | 299 | if (ArgsSupplied >= 4) pos = atoi(Pos); 300 | 301 | switch(what) { 302 | case D_SET: DoSetenv(Var, Val, shell, 0); break; 303 | case D_LOCAL: DoSetenv(Var, Val, shell, 1); break; 304 | case D_CHOOSE: DoChoose(Var, Val, shell); break; 305 | case D_ADD: 306 | case D_DEL: 307 | case D_MOVE: PathManip(Var, Val, shell, pos, what); break; 308 | default: fprintf(stderr, "%s: internal error - unknown directive %d\n", 309 | Argv[0], what); 310 | } 311 | } 312 | return 0; 313 | } 314 | 315 | /***************************************************************/ 316 | /* */ 317 | /* DoSetenv */ 318 | /* */ 319 | /* Issue the command to set an environment variable. */ 320 | /* Escape shell characters that may cause problems. */ 321 | /* */ 322 | /***************************************************************/ 323 | void DoSetenv(const char *var, const char *val, int shell, int local) 324 | { 325 | char *envstr; 326 | 327 | switch(shell) { 328 | case SH_LIKE: 329 | printf("%s=", var); 330 | PrintEscaped(val, 0); 331 | if (!local) 332 | printf("; export %s", var); 333 | printf(TrailingSemi); 334 | break; 335 | 336 | case CSH_LIKE: 337 | if (!local) 338 | printf("setenv %s ", var); 339 | else 340 | printf("set %s=", var); 341 | 342 | PrintEscaped(val, 0); 343 | printf(TrailingSemi); 344 | break; 345 | 346 | default: 347 | fprintf(stderr, "%s: internal error - bad shell value %d\n", Argv[0], shell); 348 | } 349 | 350 | /* If not reading from cmd line, set the value in the environment */ 351 | if (!UseCmdLine) { 352 | envstr = malloc(strlen(var)+strlen(val)+2); 353 | if (envstr) { 354 | sprintf(envstr, "%s=%s", var, val); 355 | putenv(envstr); 356 | } else { 357 | fprintf(stderr, "%s: out of memory!\n", Argv[0]); 358 | exit(1); 359 | } 360 | } 361 | 362 | return; 363 | } 364 | 365 | /***************************************************************/ 366 | /* */ 367 | /* SplitPath */ 368 | /* */ 369 | /* Split a colon-separated path list into its components */ 370 | /* */ 371 | /***************************************************************/ 372 | int SplitPath(char *path) 373 | { 374 | NumComponents = 0; 375 | if(!path) return 0; 376 | 377 | /* Strip leading colons, if any */ 378 | while(*path == ':') path++; 379 | if (!*path) return 0; 380 | 381 | NumComponents = 1; 382 | PathComp[0] = path; 383 | while(*path) { 384 | if (*path == ':') { 385 | *path++ = 0; 386 | while (*path == ':') path++; 387 | if (*path) { 388 | PathComp[NumComponents] = path; 389 | NumComponents++; 390 | if(NumComponents == MAXCOMPONENTS) return NumComponents; 391 | } 392 | } 393 | path++; 394 | } 395 | return NumComponents; 396 | } 397 | 398 | /***************************************************************/ 399 | /* */ 400 | /* FindCurPos */ 401 | /* */ 402 | /* Find the current position of a dir in current split path. */ 403 | /* Return 0 if not in current path. */ 404 | /* */ 405 | /***************************************************************/ 406 | int FindCurPos(const char *dir) 407 | { 408 | int i; 409 | for (i=0; i NumComponents)) { 574 | PrintEscaped(dir, 1); 575 | if (!UseCmdLine) { 576 | sprintf(s, "%s:", dir); 577 | s += strlen(s); 578 | } 579 | } 580 | 581 | /* If reading from a file, chew off the final colon in the new path 582 | and put it in environment. */ 583 | if (!UseCmdLine) { 584 | *--s = 0; 585 | putenv(envstr); 586 | } 587 | 588 | 589 | switch(shell) { 590 | case SH_LIKE: printf("; export %s%s", var, TrailingSemi); break; 591 | case CSH_LIKE: printf(TrailingSemi); break; 592 | } 593 | free(path); 594 | return; 595 | } 596 | 597 | /***************************************************************/ 598 | /* */ 599 | /* DoChoose */ 600 | /* */ 601 | /* Simple-minded: If shell is 0, print val1, else print val2 */ 602 | /* */ 603 | /***************************************************************/ 604 | void DoChoose(const char *val1, const char *val2, int shell) 605 | { 606 | switch(shell) { 607 | case SH_LIKE: 608 | PrintEscaped(val1, 0); 609 | printf(TrailingSemi); 610 | break; 611 | 612 | case CSH_LIKE: 613 | PrintEscaped(val2, 0); 614 | printf(TrailingSemi); 615 | break; 616 | 617 | default: 618 | fprintf(stderr, "%s: DoChoose - bad value for shell %d\n", Argv[0], shell); 619 | break; 620 | } 621 | } 622 | 623 | 624 | /***************************************************************/ 625 | /* */ 626 | /* Usage - print usage instructions */ 627 | /* */ 628 | /***************************************************************/ 629 | void Usage(const char *name) 630 | { 631 | fprintf(stderr, "%s (version %s) Copyright 1994-2011 by Roaring Penguin Software Inc.\n\n", 632 | name, VERSION); 633 | fprintf(stderr, "Usage:\n"); 634 | fprintf(stderr, " %s [options] set var value\n", name); 635 | fprintf(stderr, " %s [options] local var value\n", name); 636 | fprintf(stderr, " %s [options] add pathvar dir [pos]\n", name); 637 | fprintf(stderr, " %s [options] move pathvar dir pos\n", name); 638 | fprintf(stderr, " %s [options] del pathvar dir\n", name); 639 | fprintf(stderr, " %s [options] choose sh_choice csh_choice\n", name); 640 | fprintf(stderr, "\nOptions:\n"); 641 | fprintf(stderr, " -e = Do not escape shell meta-characters\n"); 642 | fprintf(stderr, " -s = Put trailing semicolon after each command\n"); 643 | fprintf(stderr, " -h = Display usage information\n"); 644 | fprintf(stderr, "\nIf no directives are given on command line, they\n"); 645 | fprintf(stderr, "are read from stdin. Multiple directives may be\n"); 646 | fprintf(stderr, "issued this way.\n"); 647 | exit(1); 648 | } 649 | 650 | /***************************************************************/ 651 | /* */ 652 | /* Init */ 653 | /* */ 654 | /* Read command-line args and options. */ 655 | /* */ 656 | /***************************************************************/ 657 | void Init(int argc, char *argv[]) 658 | { 659 | int i; 660 | char *s; 661 | 662 | /* Set global vars */ 663 | Argc = argc; 664 | Argv = argv; 665 | 666 | /* Get the options */ 667 | for (i=1; i FirstArg) { 716 | strncpy(Directive, Argv[FirstArg], MAX_DIR_LEN); 717 | Directive[MAX_DIR_LEN] = 0; 718 | ArgsSupplied++; 719 | } 720 | if (Argc > FirstArg+1) { 721 | strncpy(Var, Argv[FirstArg+1], MAX_VAR_LEN); 722 | Var[MAX_VAR_LEN] = 0; 723 | ArgsSupplied++; 724 | } 725 | if (Argc > FirstArg+2) { 726 | strncpy(Val, Argv[FirstArg+2], MAX_VAL_LEN); 727 | Val[MAX_VAL_LEN] = 0; 728 | ArgsSupplied++; 729 | } 730 | if (Argc > FirstArg+3) { 731 | strncpy(Pos, Argv[FirstArg+3], MAX_POS_LEN); 732 | Pos[MAX_POS_LEN] = 0; 733 | ArgsSupplied++; 734 | } 735 | return 1; 736 | } else if (UseCmdLine > 1) { 737 | return 0; 738 | } else { 739 | return ReadCmdFromStdin(); 740 | } 741 | } 742 | 743 | /***************************************************************/ 744 | /* */ 745 | /* ReadCmdFromStdin */ 746 | /* */ 747 | /* Read a command from stdin, handling backslash-escaped chars */ 748 | /* */ 749 | /***************************************************************/ 750 | int ReadCmdFromStdin(void) 751 | { 752 | /* Try reading the directive first */ 753 | if (!ReadEscapedToken(Directive, MAX_DIR_LEN, 0)) return 0; 754 | 755 | ArgsSupplied = 1; 756 | 757 | /* Read var, value and pos */ 758 | if (ReadEscapedToken(Var, MAX_VAR_LEN, 1)) { 759 | ArgsSupplied++; 760 | if (ReadEscapedToken(Val, MAX_VAL_LEN, 1)) { 761 | ArgsSupplied++; 762 | if (ReadEscapedToken(Pos, MAX_POS_LEN, 1)) ArgsSupplied++; 763 | } 764 | } 765 | return 1; 766 | } 767 | 768 | /***************************************************************/ 769 | /* */ 770 | /* ReadEscapedToken */ 771 | /* */ 772 | /* Read a token from stdin. */ 773 | /* */ 774 | /***************************************************************/ 775 | int ReadEscapedToken(char *buf, int len, int eoln_flag) 776 | { 777 | static int seen_eoln = 0; 778 | int nread = 0; 779 | char ch; 780 | 781 | if (!eoln_flag) seen_eoln = 0; 782 | 783 | /* Reached the end of current line -- return 0 */ 784 | if (seen_eoln) return 0; 785 | 786 | /* Skip whitespace */ 787 | ch = getchar(); 788 | if (ch == EOF) return 0; 789 | while (isspace(ch)) { 790 | if (ch == '\n' && eoln_flag) { 791 | seen_eoln = 1; 792 | return 0; 793 | } 794 | ch = getchar(); 795 | } 796 | 797 | /* Read 'len' escaped chars */ 798 | while (nread < len) { 799 | if (ch == EOF) return 0; /* EOF reached */ 800 | if (ch == '\\') { 801 | ch = getchar(); 802 | if (ch == EOF) return 0; 803 | *buf++ = ch; 804 | nread++; 805 | ch = getchar(); 806 | continue; 807 | } else if (isspace(ch)) break; 808 | else { 809 | *buf++ = ch; 810 | nread++; 811 | ch = getchar(); 812 | } 813 | } 814 | 815 | /* If we didn't halt because of whitespace, skip to next whitespace */ 816 | while (ch != EOF && !isspace(ch)) ch = getchar(); 817 | 818 | if (ch == '\n') seen_eoln = 1; 819 | *buf = 0; 820 | return 1; 821 | } 822 | --------------------------------------------------------------------------------