From f43c79154259268abb7a0c9f265dc1d78e5b62d9 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Fri, 9 May 2025 00:43:54 -0500 Subject: [PATCH] Alias support. --- tests/sh.test | 6 ++ toys/pending/sh.c | 148 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 147 insertions(+), 7 deletions(-) diff --git a/tests/sh.test b/tests/sh.test index 008fee1d..04b7710b 100644 --- a/tests/sh.test +++ b/tests/sh.test @@ -736,6 +736,12 @@ testing 'if; is a syntax error but if $EMPTY; is not' \ testing 'trap1' $'trap \'echo T=$?;false\' USR1;kill -s usr1 $$;echo A=$?' \ 'T=0\nA=0\n' '' '' +shxpect 'alias' I$'alias abc=whoami\n' E"$P" I$'abc\n' O"$USER"$'\n' +shxpect 'alias recursion' I$'alias echo="abc 123" abc="echo 456"\n' E"$P" \ + I$'echo 789\n' O$'456 123 789\n' +shxpect 'alias cross token' I$'alias abc="cat <<"\n' E"$P" I$'abc1", TOYFLAG_NOFORK)) USE_SH(NEWTOY(cd, ">1LP[-LP]", TOYFLAG_NOFORK)) USE_SH(NEWTOY(continue, ">1", TOYFLAG_NOFORK)) @@ -61,6 +62,7 @@ USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK)) USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK)) USE_SH(OLDTOY(., source, TOYFLAG_NOFORK)) USE_SH(NEWTOY(trap, "lp", TOYFLAG_NOFORK)) +USE_SH(NEWTOY(unalias, "<1a", TOYFLAG_NOFORK)) USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK)) USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK)) @@ -137,6 +139,20 @@ config SH bg fg jobs kill # These are here for the help text, they're not selectable and control nothing +config ALIAS + bool + default n + depends on SH + help + usage: alias [NAME[=VALUE]...] + + Create or show macro expansions, which replace the name of a command with + a string when reading input lines (but only in interactive mode, not when + running scripts or -c input). Historical, mostly replaced by functions. + + With no arguments, display all available aliases. Names with no = display + that existing alias (error if undefined). + config BREAK bool default n @@ -329,6 +345,17 @@ config TRAP The special signal EXIT gets called before the shell exits, RETURN when a function or source returns, and DEBUG is called before each command. +config UNALIAS + bool + default n + depends on SH + help + usage: unalias [-a] [NAME...] + + Remove existing alias (error if none). + + -a Remove all existing aliases. + config WAIT bool default n @@ -368,7 +395,7 @@ GLOBALS( char *name; struct sh_pipeline { // pipeline segments: linked list of arg w/metadata struct sh_pipeline *next, *prev, *end; - int count, here, type; + short count, here, type, noalias; long lineno; struct sh_arg { char **v; @@ -378,6 +405,7 @@ GLOBALS( unsigned long refcount; } **functions; long funcslen; + struct sh_arg alias; // runtime function call stack. TT.ff is current function, returns to ->next struct sh_fcall { @@ -3073,13 +3101,15 @@ static struct sh_pipeline *add_pl(struct sh_pipeline **ppl, struct sh_arg **arg) // Add a line of shell script to a shell function. Returns 0 if finished, // 1 to request another line of input (> prompt), -1 for syntax err +// Attaches parsed input data to TT.ff->pl static int parse_line(char *line, struct double_list **expect) { - char *start = line, *delete = 0, *end, *s, *ex, done = 0, + char *start = line, *delete = 0, *end, *s, *ss, *ex, done = 0, *tails[] = {"fi", "done", "esac", "}", "]]", ")", 0}; struct sh_pipeline *pl = TT.ff->pl ? TT.ff->pl->prev : 0, *pl2, *pl3; struct sh_arg *arg = 0; - long i; + struct arg_list *aliseen = 0, *al; + long i, j; // Resume appending to last statement? if (pl) { @@ -3207,7 +3237,6 @@ here_end: i = ex && !strcmp(ex, "esac") && ((pl->type && pl->type != 3) || (*start==';' && end-start>1)); if (i) { - // Premature EOL in type 1 (case x\nin) or 2 (at start or after ;;) is ok if (end == start) { if (pl->type==128 && arg->c==2) break; // case x\nin @@ -3260,9 +3289,55 @@ here_end: continue; } - // Save word and check for flow control - arg_add(arg, s = xstrndup(start, end-start)); + // Copy word and check for aliases + s = xstrndup(start, end-start); + if (TT.alias.c && !pl->noalias) { + // ! x=y and xnoalias = 2; + start = 0; + } + if (start) { + // It's the command, is it a recognized alias? + for (j = 0; jnext) { + ss = al->arg; + if (strstart(&ss, s) && *ss=='=') break; + } + if (!al) break; + } + if (j==TT.alias.c) start = 0; + } + + // Did we find an alias? + if (start) { + (al = xmalloc(sizeof(struct arg_list)))->next = aliseen; + al->arg = TT.alias.v[i]; + aliseen = al; + start = end = xmprintf("%s%s", start, end); + free(delete); + delete = start; + + continue; + } + if (!pl->noalias) pl->noalias = 1; + } start = end; + arg_add(arg, s); + + if (pl->noalias==2) { + pl->noalias = 0; + + continue; + } + if (pl->noalias) while (aliseen) free(llist_pop(&aliseen)); // Second half of case/esac parsing if (i) { @@ -4424,6 +4499,34 @@ void sh_main(void) /********************* shell builtin functions *************************/ +#define FOR_alias +#include "generated/flags.h" +void alias_main(void) +{ + char *s; + int i, j; + + if (!toys.optc || FLAG(p)) + for (i = 0; iarg.c = ii; } +#define FOR_unalias +#include "generated/flags.h" + +void unalias_main(void) +{ + char *s; + int i, j; + + // Remove all? + if (FLAG(a)) { + for (i = 0; i