5

My .bashrc sets up a bunch of aliases for me to use as needed, and then runs one of them automatically.

Turns out this caused some problems with automated scripts ssh-ing into my machine, as opposed to using an interactive shell. So, to fix this, I put them inside an if block so they wouldn't be defined or run for those automated scripts...

if [ -n "$TERM" ] && [ "$TERM" != "dumb" ] ; then
    alias short='long command here'
    alias another='very long command here'
    # ...

    short
fi

Only to see short: command not found!

Let's reduce this to the minimum...

$ cat alias.sh
alias foo='echo hi'
foo
$ sh alias.sh
hi
cat alias-in-if.sh
if true ; then
        alias foo='echo hi'
        foo
fi
sh alias-in-if.sh
alias-in-if.sh: line 3: foo: command not found

Why does the first script work, and not the second?

(I've answered my own question.)

  • FWIW, bash won't work even with the non-if alias.sh. I guess you tried it on an ubuntu or debian system where sh is dash, not bash (In bash, you'll have to use shopt -s expand_aliases in scripts). – mosvy Jul 25 '19 at 3:21
  • I tested this both on CentOS 7 and on Arch Linux. In both cases, sh is bash. I've never heard of that option, so I assume it's always on by default if the script is ultimately being run from inside an interactive session? – Keiji Jul 25 '19 at 11:51
  • 1
    Yes, calling bash as sh or with the --posix switch turns that on (since the standard makes no allowances for not expanding aliases in scripts). You'll have to turn it on explicitly if your script is using a #! /bin/bash shebang, though. – mosvy Jul 25 '19 at 12:29
  • 1
    And of course, that's not documented in the bash(1) manpage, but in the texinfo manual, which is not installed by default on many distros. – mosvy Jul 25 '19 at 12:39
3

I searched around and didn't find anything to do with aliases inside ifs, only aliases inside functions, which seemed a different issue at first.

But actually, this answer solves it:

Bash always reads at least one complete line of input before executing any of the commands on that line. [...] The commands following the alias definition on that line are not affected by the new alias. [...] To be safe, always put alias definitions on a separate line, and do not use alias in compound commands.

Seems this isn't just limited to functions, but if statements as well. The entire if block is treated as a single line. The solution, to ensure the alias is only defined and executed if the condition is true, yet ensure it isn't even defined if the condition is false, is to evaluate the condition twice like this:

cat alias-before-if.sh
if true ; then
        alias foo='echo hi'
fi

if true; then
        foo
fi
$ sh alias-before-if.sh
hi

So my original script would be:

if [ -n "$TERM" ] && [ "$TERM" != "dumb" ] ; then
    alias short='long command here'
    alias another='very long command here'
    # ...
fi

if [ -n "$TERM" ] && [ "$TERM" != "dumb" ] ; then
    short
fi
  • 2
    Do not evaluate the condition twice. Evaluate it once, save the result and use the result twice. Not only this is DRY; in general the approach will avoid (at least some) bugs if the condition depends on time, existence of files, anything external, so it can evaluate differently each time. – Kamil Maciorowski Jul 24 '19 at 21:09
  • Normally I'd agree. Shell scripting is a different case though: you cannot simply store a Boolean result for later. You'd need to evaluate the condition, store $? in a variable, then compare this to zero in both ifs: two extra statements to remove the "and" from the doubled condition didn't seem worthwhile. – Keiji Jul 25 '19 at 11:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.