SomeKittens
SomeKittens

Reputation: 39522

Set alias within bash script

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

Answers (1)

Austin Phillips
Austin Phillips

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

Related Questions