For learning purposes, I would like to automatically print out the aliases I use each time I run them. My hope is that it will help me learn what some of the useful flags involved in the commands do, and also keep my memory fresh for pieces in aliases involving pipelines.
I know I can just run type alias-name
each time I'd like to remember, but I want this to happen automatically.
So far I have this script which I source from ~/.bashrc
declare -a ALIASES
while read -r; do
ALIASES+=("$REPLY")
done < <(alias)
for i in "${ALIASES[@]}"
do
ALIAS_AND_NAME=$(awk -F '=' '{print $1}' <<< "$i") # puts the keyword alias and the alias itself into the variable
NAME_ONLY=$(awk '{print $2}' <<< "$ALIAS_AND_NAME") # removes the keyword alias
COMMAND_AFTER_EQUALS=$(sed -e 's/^[^=]*=//g' <<< "$i")
# regex: matches up until the '=', then also matches '=', and replaces the match with nothing (removes the alias piece)
ALIAS_COMMAND=$(sed -e "s/^'//" -e "s/'$//" <<< "$COMMAND_AFTER_EQUALS")
# remove single quotes
# now remake the alias, first echoing what the alias is, then running the command
alias $NAME_ONLY="echo ~~~alias $NAME_ONLY=\'$ALIAS_COMMAND\'~~~ 1>&2; $ALIAS_COMMAND"
done
As far as I can tell, this works just as I'd like whenever an alias is used standalone. The issue I'm running into now is when aliased commands like
alias grep='grep --color=auto'
are used in pipelines. I believe now the input is going from the previous pipeline command into the echo statement in the new alias. Is there a way to avoid this?
You can't use an alias on its own for what you're trying to do -- but it can be pulled off if you pair that alias with a function:
write_alias_warning_and_execute() {
local orig_alias_name orig_alias_str orig_alias_q args_q
orig_alias_name=$1; shift
orig_alias_str=$1; shift
printf -v args_q '%q ' "$@"
printf -v orig_alias_q '%q' "$orig_alias_str"
echo "alias $orig_alias_name=$orig_alias_q" >&2
eval "command $orig_alias_str $args_q"
}
for alias_name in "${!BASH_ALIASES[@]}"; do
alias_content=${BASH_ALIASES[$alias_name]}
if [[ $alias_content = *write_alias_warning_and_execute* ]]; then
echo "alias $alias_name is already wrapped; skipping" >&2
fi
printf -v new_alias 'write_alias_warning_and_execute %q %q' "$alias_name" "$alias_content"
BASH_ALIASES[$alias_name]=$new_alias
done
Some notes, stylistic and otherwise:
alias
command, because (in the reserved meaningful-to-the-shell-itself all-caps namespace) bash exposes an associative array, BASH_ALIASES
, containing the names and content of all defined aliases.printf %q "$var"
(for compatibility: bash 5.0+ allows the newer and better ${var@Q}
) ensures that we're escaping our strings in a way that evaluates back to their original text, thereby avoiding injection attacks and data-centric bugs as are otherwise associated with eval
.command
builtin is used to prevent recursion, ensuring that our eval
doesn't re-invoke the alias itself.alias grep='grep --color'
, one can run grep() { command grep --color "$@"; }
. In that context, adding additional commands to the function can look like grep() { echo "adding --color to grep" >&2; command grep --color "$@"; }