330

In my ~/.bashrc file reside two definitions:

  1. commandA, which is an alias to a longer path
  2. commandB, which is an alias to a Bash script

I want to process the same file with these two commands, so I wrote the following Bash script:


#!/bin/bash

for file in "$@"
    do
    commandA $file
    commandB $file
done

Even after logging out of my session and logging back in, Bash prompts me with command not found errors for both commands when I run this script.

What am I doing wrong?

ilkkachu
  • 133,243
  • 15
  • 236
  • 397
Zaid
  • 10,442
  • 13
  • 38
  • 36
  • 13
    BTW, there's no need to log in and out to have an alias recognized. You need just do `source ~/.bashrc`. – tshepang Nov 25 '10 at 19:10
  • For my case I was connected by SSH agent remotely, after adding alias as I closed the SSH agent and connected again it started working. – dav Sep 06 '15 at 05:05
  • An alias is a way of shortening a command. (They are only used in interactive shells and not in scripts — this is one of the very few differences between a script and an interactive shell.) – LF00 Sep 12 '19 at 10:18

5 Answers5

272

If you look into the bash manpage you find:

Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt (see the description of shopt under SHELL BUILTIN COMMANDS below).

So put a

shopt -s expand_aliases

in your script.

Make sure to source your aliases file after setting this in your script.

shopt -s expand_aliases
source ~/.bash_aliases
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
ddeimeke
  • 4,517
  • 1
  • 19
  • 18
  • 19
    I placed it in my script, but it's still not working. Same error. – Zaid Sep 02 '10 at 10:29
  • 8
    Adding `shopt -s expand_aliases source ~/.bash_aliases` works perfectly for me. Often there is a form of interactive shell detection in .bashrc like this: `# If not running interactively, don't do anything [ -z "$PS1" ] && return` @Zaid, Maybe you want to check for that in the file you sourced. – Frank Schubert Apr 04 '13 at 01:46
  • 3
    Curiously, `shopt -s expand_aliases` doesn't have to go before the alias definition but before the alias use. Adding to @FrankSchubert: Interactive shell detection may also be done using `$-` which contains the options for the shell, specifically `i` if the shell is interactive. – valid Mar 20 '15 at 14:12
  • 6
    this isn't a correct answer... Sourcing your aliases inside your script isn't the answer. Your `~/.bash_aliases` might depend on other stuff previously loaded on an interactive shell... The closest thing I found is changing your hashbang to `#!/bin/bash -li` Still not perfect. Ideally you should use functions and not aliases. – Stefanos Kalantzis Feb 05 '16 at 11:47
  • @StefanosKalantzis I don't agree. `~/.bash_aliases` might depend on other stuff but in most cases NOT. In my case `#!/bin/bash -i` or `-li` doesn't solve the problem because I've been using `keychain` to autoload ssh keys. So each time I try run my script with `bash -i` my whole `~/.bashrc` was running as well with `keychain` and print all keys in terminal. I see above solution as most precise then `bash -i`. – sobi3ch Jun 01 '16 at 19:08
  • @sobi3ch I believe this is way too user specific, and there's no right or wrong. At the end of the day, what hop said in the accepted answer is the correct thing. Just don't depend on your aliases in a bash script. it's just wrong ! – Stefanos Kalantzis Jun 02 '16 at 10:56
  • This did not work for me. But putting the first line in my script and then setting aliases worked. So putting eg. this into my script works in my case: `shopt -s expand_aliases` ⏎ `alias my_alias="ls"`. Edit: `#!/bin/bash -i` as shebang also worked. – JustAC0der Sep 25 '18 at 11:58
  • Another gotcha: https://unix.stackexchange.com/a/502261/233125 – davidvandebunte Mar 22 '19 at 15:31
  • This is the actual answer, because functions' contents are evaluated when they are defined, whereas an alias is evaluated during its execution. So if you need to run a command, that is 1. imported from a sourced script 2. needs to be evaluated in the current script, then you need to use an alias. – Akito Jan 29 '20 at 18:24
  • Thanks. My problem was that I sourced it before setting expand_aliases. – Geremia Dec 17 '20 at 18:16
  • 2
    Alas, this doesn't work with `bash -c`, for example `bash -c "shopt -s expand_aliases; source my-script.sh; some-alias`. Still get `some-alias: command not found` – Hubro Feb 17 '22 at 23:00
160

First of all, as ddeimeke said, aliases by default are not expanded in non-interactive shells.

Second, .bashrc is not read by non-interactive shells unless you set the BASH_ENV environment variable.

But most importantly: don't do that! Please? One day you will move that script somewhere where the necessary aliases are not set and it will break again.

Instead set and use variables as shortcuts in your script:

#!/bin/bash

CMDA=/path/to/gizmo
CMDB=/path/to/huzzah.sh

for file in "$@"
do
    $CMDA "$file"
    $CMDB "$file"
done
mtraceur
  • 1,146
  • 9
  • 14
  • 7
    That solution doesn’t work for the usual `alias` use cases. E.g. `alias mv="mv -v --backup=numbered"`. – Evi1M4chine Apr 23 '16 at 20:11
  • @Evi1M4chine: Yes, it does. At least after I reverted Gilles unnecessary edit. But it might be better to use a different variable for the parameters, anyway. –  Apr 24 '16 at 15:50
  • 1
    Ah, note the *lack* of quotes around `$CMDA` / `$CMDB`… Apart from uppercase variables being reserved for bash itself in bash, and it indeed working, that lack of quotes makes me really uneasy… Thanks anyway. – Evi1M4chine Apr 30 '16 at 00:26
  • @Evi1M4chine: Uh, what? 1. I removed the quotes myself in the most recent edit. 2. where do you get the "reserved for bash itself" from? this would be the first I've heard of it. 3. If _that_ makes you uneasy, how do you feel about using bash in the first place? Anyway, use a separate variable for the options as I told you. –  Apr 30 '16 at 17:46
  • 1. From experience, unquoted variables have the bad habit of blowing up in one’s face. E.g. when the variable is empty, one expects it to have no spaces but it does, etc. Here, the latter is intended, but it still makes me uneasy. :) 2. Turns out it’s something in-between: http://wiki.bash-hackers.org/scripting/style#syntax_and_coding_guidelines → If you use uppercase variables, there’s a chance that a later version of bash uses that name itself, breaking your script. 3. Don’t worry. You seem to have mistaken my comment for criticism. It wasn’t. :) – Evi1M4chine May 01 '16 at 21:08
  • How about `CMDA="$(which gizmo)"`? – alvas Apr 05 '17 at 06:42
  • 1
    @alvas: the assumption is that `gizmo` is not on the path or there is a command with the same name but a higher priority. else you could simple set `CMDA` to plain `gizmo` in the first place. –  Apr 05 '17 at 09:20
  • @Evi1M4chine The lowercase rule applies for local and script variables. In contrast, environment variables are typically uppercase. Prefix should be enough to prevent collision since this is what everyone has been doing: [Bundler](https://bundler.io/v1.14/man/bundle-config.1.html#LIST-OF-AVAILABLE-KEYS) is using `BUNDLE_`, [OpenSSH](http://man.openbsd.org/ssh#ENVIRONMENT) is using `SSH_`. If you can't survive the prefix way, you are probably exposing too many environment variables (so you are doomed anyway). – Franklin Yu Apr 09 '17 at 06:47
  • I'm not convinced with the reasoning that script can break when it is moved. The same can happen if path is set by environment variable. The path to command may change when script is moved to a different environment. Or command may not be installed in new environment. So, when script moves, user will have to take care of environment changes anyway. No? – rineez Jan 30 '18 at 03:56
  • @rineez: except this way it's _right there in the script_ –  Jan 30 '18 at 14:24
  • It's a drop-in replacement. What is the correct way without duplicating every script for use with that drop-in replacement? – J.Money Dec 31 '19 at 02:22
70

Aliases can't be exported so they're not available in shell scripts in which they aren't defined. In other words, if you define them in ~/.bashrc they're not available to your_script.sh (unless you source ~/.bashrc in the script, which I wouldn't recommend but there are ways to do this properly).

However, functions can be exported and would be available to shell scripts that are run from an environment in which they are defined. This can be done by placing this in your bashrc:

foo()
{
    echo "Hello World!"
}
export -f foo

As the Bash manual says, "For almost every purpose, shell functions are preferred over aliases."

Dennis Williamson
  • 6,620
  • 1
  • 34
  • 38
  • 3
    While this doesn't technically answer the question, as you say you can simply replace `alias commandA=...` with `commandA() { ... }` then `export commandA` and you get identical behavior to the alias. So this is pretty much an identical alternative to aliases as far as I know that works great in bash scripts – Phylliida May 21 '18 at 18:35
  • The problem here is when you need an alias that you pass lots of quoted options to. for instance as an example, `alias b=bash` makes it easy to do `b -c "echo test | grep test"` which would be difficult to do in functions. – Fmstrat Aug 13 '19 at 16:03
  • @Fmstrat: `b () { bash "$@"; }` then `b -c "echo test | grep test"` – Dennis Williamson Aug 13 '19 at 16:10
  • This does not solve the problem when the contents of the function needs to be evaluated in the script that got the function from another script... – Akito Jan 29 '20 at 18:10
  • @Akito: I'm not sure what you mean. It works for me. – Dennis Williamson Jun 28 '22 at 23:46
  • quote: *unless you source ~/.bashrc in the script*, not really, "source ~/.bashrc" does not allow alias to be used in scripts either. – Tiina Feb 17 '23 at 06:33
  • @Tiina: The first part of that quote is "if you define them in ~/.bashrc". Of course, you also need to `shopt -s expand_aliases` in the script. – Dennis Williamson Feb 17 '23 at 13:38
20
[cmd line] > bash -i [your script's file path]

The i is for interactive and sources your bash profile for you.

enedil
  • 1,674
  • 4
  • 17
  • 32
user65576
  • 209
  • 2
  • 2
14

You can also use . before your script. Run it as:

. /path/to/your/script.sh

You can check for this condition on your bash script as follows:

if [[ $- == *i* ]]
then
    echo "running on interactive mode. aliases will work!"
fi
tinlyx
  • 536
  • 1
  • 5
  • 22
Tono Nam
  • 268
  • 2
  • 4
  • 2
    For a solution which avoids editing the script itself (that is why a script exists for us so it can be shared among many people) this is really great – demongolem Mar 30 '21 at 12:22
  • 1
    Sure, I wouldn't put it into a professional production script, but I just needed something quick and scrappy +1 – MANA624 Aug 17 '21 at 19:13