Reputation: 39522
I'm trying to create an alias in a script like so:
#!/bin/bash
shopt -s expand_aliases
#Currently just using 'git' to try and get it working
#It'll be replaced with the following line when it works
#compgen -c | while read x;
echo 'git' | while read x;
do
a=`echo $x | tr '[:lower:]' '[:upper:]'`;
echo $a;
echo $x;
if [ "$a" != '' ]
then
echo 'got here';
alias $a=$x;
alias;
GIT;
fi
done
While a simple test script works (testme
is added to the aliases in my current shell):
#!/bin/bash
shopt -s expand_aliases
alias testme="echo It Worked"
alias
testme
Output from first script:
GIT
git
got here
alias GIT='git'
alias grep='grep --colour=auto'
alias ls='ls --color=auto'
GIT: command not found
GIT
shows up in the aliases, but can't be executed. I've tried calling it with . ./script.sh
and source script.sh
. What am I doing wrong?
Upvotes: 3
Views: 6222
Reputation: 15776
It's likely a problem of when the alias is expanded. The bash man page has the following to say:
The rules concerning the definition and use of aliases are somewhat confusing. Bash always reads at least one complete line of input before executing any of the commands on that line. Aliases are expanded when a command is read, not when it is executed. ...
There's more info in the man page, but when executing a compound statement such as the list within your while
loop, the expansion will occur when when the input is first read, not when executed. Since GIT
is not defined when the input is read, there will be an error.
The following snippet shows the issue:
#!/bin/bash
shopt -s expand_aliases
while read x; do
a=`echo $x | tr '[:lower:]' '[:upper:]'`
alias $a=$x
GIT # <--- this fails to execute git
done < <(echo 'git')
GIT # <--- this executes git
Once the while loop has executed, then the alias becomes available. Details in the man page are a bit sketchy, but it appears as though the comments for aliases defined in functions would apply.
Also, be careful when using syntax such as echo foo | while read x; do stuff; done
. The while
loop will execute in a subshell, any changes made in the subshell will not persist beyond the while
loop. The example above shows a method so that the while loop executes in the current shell, but takes its input from a command which is executed.
Update:
If the above script were written in its original form it would look like this:
#!/bin/bash
shopt -s expand_aliases
#Currently just using 'git' to try and get it working
#It'll be replaced with the following line when it works
#compgen -c | while read x;
echo 'git' | while read x; do
a=`echo $x | tr '[:lower:]' '[:upper:]'`
alias $a=$x
GIT # <--- this fails to execute git
done
GIT # <--- this also fails executes git because the alias' were set in a subshell.
In this case, the alias is never executed. The reason for this is that the pipe |
stars a new subshell and links the processes together. The echo
runs in one process, the while read ...
runs in the other, linked by a pipe. When the aliases are set, they are set in the environment of the process of the subshell running the while
loop. However, when the while
loop has finished, that process terminates and its environment is discarded, along with any changes made to its environment such as the addition of aliases. As a result, the second attempt to execute GIT
will fail as the alias information has been discarded.
When you use the topology shown in the first script, the while
loops runs in the current script, so any changes made to the environment persist past the end of the loop.
You will also notice this behaviour if you execute your script, rather than sourcing it. If you expect to be able to configure a set of aliases in a script like this, run the script, have them available in your environment, you have the same problem because your script runs in a separate process to the parent shell, creates the aliases, which are subsequently discarded when the script exits.
$ ./create_aliases
$ alias # <--- Ooops, no aliases defined.
You'd have to source your script like this, which will run the contents of the script in the current process, thus modifying the current environment.
$ source ./create_aliases
$ alias # <--- Yay, new aliases present.
This is why bash will source files such as ~/.bashrc
, so that changes you make in that file will exist in the current environment.
Upvotes: 8