0

I am trying to define and use alias within bash -c with a one-line command.

The command:

bash -c "eval $'df'"

works fine, but:

bash -c "eval $'alias df5=df\ndf5 -h'"

doesn't. Why, and how can I define and use alias within bash -c with a one-line command?


From Kusalananda's answer on how can I write an eval command containing a new line into one line?:

"The $'...' is a "C string", and bash would expand the \n within it to a literal newline before passing it to eval.

Therefore my understanding is that one has to use ' within the eval. Additionally, since the Bash manual says:

Enclosing characters in single quotes (') preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash.

My understanding is that one should use " outside the eval, since there is ' inside the eval.


Comments:

  • See How can I write an eval command containing a new line into one line? for the explanation of the use of \n instead of ;
  • The motivation behind the question is that in my actual entire command, I use the aliased command for other things (the command looks like docker run -it ubuntu:18.04 bash -c "eval $'alias pip=pip3\nsource blah.sh; exec bash"). where blah.sh uses pip. The full actual command is: docker run --interactive --tty ubuntu:18.04 bash -c "apt update; apt install -y git nano wget htop python3 python3-pip unzip; git clone https://github.com/KhalilMrini/LAL-Parser; cd LAL-Parser/; alias pip=pip3; source requirements.sh; apt-get install -y libhdf5-serial-dev; alias python=python3 ; source parse.sh; exec bash", but I need to add eval around alias. The command launches a Docker container, installs some requirements and runs some Python code.
  • The command is aimed for a non-technical colleague: I'd prefer them to run just a one-line command so that it's easy for them to use. As a result I'd prefer not to have to require a file (e.g., a Dockerfile or bash script) for the command to work.
  • The following command:

    bash -c "
        eval 'alias df5=df
        df5 -h'
    "
    

    as well as the command

    bash -c "
        alias df5=df
        df5 -h
    "
    

    also don't work. Error bash: line 2: df5: command not found. As a result it seems that the issue is that alias doesn't work within bash -c. I don't know why and wonder whether there is some workaround.

1
  • Comments are not for extended discussion; this conversation has been moved to chat. – terdon May 14 '20 at 8:36
5

After edited question:

To the edited question, now "no need for interactive aliases after an exec bash" :

Yes, bash -c fails in:

$ bash -c "
       alias df5=df
       df5 -h
  "
bash: line 2: df5: command not found

But not because the alias fails to be set:

$ bash -c "
       alias df5=df
       alias df5
  "
alias df5=df

But because in non-interactive shells (mostly scripts) an alias doesn't get expanded by default, you need to do:

bash -c "
       alias df5=df
       shopt -s expand_aliases
       df5 -h
  "

That is one strong reason for using functions, not aliases.

If that is all that you need to overcome, then use:

docker run --interactive --tty ubuntu:18.04 \
    bash -c "
        shopt -s expand_aliases;
        apt update; 
        apt install -y git nano wget htop python3 python3-pip unzip;
        git clone https://github.com/KhalilMrini/LAL-Parser;
        cd LAL-Parser/; 
        alias pip=pip3; 
        source requirements.sh; 
        apt-get install -y libhdf5-serial-dev; 
        alias python=python3;
        source parse.sh;
        exec bash
    "

It should work as a one-liner (removing newlines). Comment if it doesn't.


Before edited question:

Previous answer addressing the issue that inside the interactive shell started by docker all new aliases get lost if at the end there is a exec bash.

The actual problem that is blocking the use of aliases inside the new interactive shell is that the whole command:

docker run --interactive --tty ubuntu:18.04 \
    bash -c "
        apt update; 
        apt install -y git nano wget htop python3 python3-pip unzip;
        git clone https://github.com/KhalilMrini/LAL-Parser;
        cd LAL-Parser/; 
        alias pip=pip3; 
        source requirements.sh; 
        apt-get install -y libhdf5-serial-dev; 
        alias python=python3;
        source parse.sh;
        exec bash
    "

Ends on a exec bash. That starts a new, clean instance of bash with no aliases nor functions from the script executed.

One possible solution is to replace the exec bash with:

bash --rcfile <(echo '. ~/.bashrc; alias pip=pip3; alias python=python3')

Which will leave the user (who runs the docker command) inside an interactive shell with two aliases defined.

Note: The idea has been un-tested, as it implies a lot of installed packages, but should probably work.

At minimum, it points to the real problem on your command.

3
  • Thanks for the analysis. It seems that the issue is not exec bash but instead bash -c, see the 2 commands I have just added at the end of the question. What do you think? – Franck Dernoncourt May 10 '20 at 3:05
  • 1
    Some ideas added to the answer, What do you think @FranckDernoncourt ?? – ImHere May 10 '20 at 3:47
  • Thanks very much for the edit, this now makes 100% sense to me. I didn't know that in non-interactive shells an alias doesn't get expanded by default. Both the bash and the docker command work – Franck Dernoncourt May 10 '20 at 4:41
2

With bash, the reason for not expanding aliases is that bash (unlike other shells) does not expand aliases when not in interactive mode.

Besides this bash specific deviation, there are more effects that are important with respect to alias expansion. The next version of the POSIX standard will contain a related description that exlpains when aliases are not granted to work.

Aliases are expanded inside the lexer part of the parser and an alias is only expanded in case that it is known by the shell and if the lexer is called with a related piece of text after the shell has become aware of that alias.

For a better understanding of alias expansion, it is therefore important to also understand how parsing is done by the shell.

The shell typically parses a whole line of input, then interprets the results (here is when an alias command becomes recognised) and continues with parsing the next line.

This explains why in the best case, an alias will first be expanded on the next line of code.

With a traditional shell like the Bourne Shell and ksh, this still does not work in some cases. The reason is that traditional shells parse some input not line by line but block-wise as a whole, before starting to interpret the results. The following constructs are subject to block-wise as a whole parsing:

  • The whole cmd-arg from shell -c "cmd-arg", regardless of it's size

  • eval command arguments

  • command substitutions

  • dot-scripts executed at shell startup

  • scripts executed via the . buitlin command

If a command in such cases is run in a sub-shell, the defined aliases never become effective. In the other cases, the aliases become effective after the parsed block is executed and the next input is parsed.

People who like to verify this may like to check for the NLFLG in Bourne Shell or ksh sources. If this flag is used for the parser, then a new-line is treated the same way as if a ; had been seen.

As you see, there are strong resons for not using aliases inside a script, even if the currenly used shell may work the intended way, it is not granted for other shells to behave the same.

2
  • 1
    Why did this answer get downvoted? – Franck Dernoncourt May 10 '20 at 13:33
  • 1
    @FranckDernoncourt there are some downvoting trolls that downvote all my answers. – schily May 10 '20 at 13:59

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.