Bash: alias in bash -c - bash

To be short:
How could I execute a
bash -c "set -x; alias ll='ls -l -h'; ll"
?
The output is
+ alias 'll=ls -l -h'
+ ll
bash: ll: command not found
Seems bash will add a single quote before my 'll'...
Thanks in advance!

There are two things preventing the alias ll from being expanded:
Aliases are only expanded in an interactive shell or if you use shopt -s expand_aliases.
Aliases are expanded (if they are expanded) when the line containing the command is read, not when it is executed, so only aliases defined on previous lines can be expanded. (Also, bash reads an entire compound command before executing it, so you can't define an alias and use it in the same compound command. That doesn't apply here but it might be useful to know.)
So you need to both mark the bash as interactive and introduce a newline character into the command line. You could, for example, do this:
bash -ic "alias ll='ls -l -h'"$'\n'"ll"
But it would be a lot easier to use a function:
bash -c 'll() { ls -l -h "$#"; }; ll'
All of that is documented in the Bash manual, §6.6 Aliases:
Aliases are not expanded when the shell is not interactive, unless the expand_aliases shell option is set using shopt…
…Bash always reads at least one complete line of input, and all lines that make up a compound command, before executing any of the commands on that line or the compound command. Aliases are expanded when a command is read, not when it is executed. Therefore, an alias definition appearing on the same line as another command does not take effect until the next line of input is read. The commands following the alias definition on that line are not affected by the new alias.
That section ends with the advice to use functions instead:
For almost every purpose, shell functions are preferred over aliases.

To be short:You can't.
According to man bash:
The rules concerning the definition and use of aliases are somewhat
confusing. Bash always reads at least one complete line of input, and
all lines that make up a compound command, before executing any of the
commands on that line or the compound command. Aliases are expanded
when a command is read, not when it is executed. Therefore, an
alias definition appearing on the same line as another command does
not take effect until the next line of input is read. The commands
following the alias definition on that line are not affected by the
new alias. This behavior is also an issue when functions are
executed. Aliases are expanded when a function definition is read,
not when the function is executed, because a function definition is
itself a command. As a consequence, aliases defined in a function
are not available until after that function is executed. To be safe,
always put alias definitions on a separate line, and do not use alias
in compound commands.
For almost every purpose, aliases are superseded by shell functions.
Use functions instead:
bash -c "ll(){ ls -l -h; }; ll"

Related

Difference between a semicolon and a new line in bash ("source rcfile; foo" vs. "source rcfile<NEWLINE>foo" where "foo" is an alias defined in rcfile)

I thought that giving a semicolon in bash is the same as giving a new line, just combining multiple lines.
However, with alias sudo='sudo -i' in the .bashrc file, the below result doesn't seem to work as I expected:
USER#HOST:~:$ cat tmp-1.sh
#!/bin/bash
source ~/.bashrc; sudo env | grep PATH
USER#HOST:~:$ cat tmp-2.sh
#!/bin/bash
source ~/.bashrc
sudo env | grep PATH
USER#HOST:~:$ ./tmp-1.sh
PATH=/usr/bin:/bin:/usr/sbin:/sbin
USER#HOST:~:$ ./tmp-2.sh
PATH=/home1/client/bin:/usr/python-3.8.2-r2/bin:/usr/jdk64/jdk1.8.0_112/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/bin:/root/bin
What's the difference between these two?
As in your comment:
alias sudo='sudo -i' is in .bashrc file
According to man bash:
The rules concerning the definition and use of aliases are somewhat confusing. Bash always reads at
least one complete line of input, and all lines that make up a compound command, before executing any
of the commands on that line or the compound command. Aliases are expanded when a command is read,
not when it is executed. Therefore, an alias definition appearing on the same line as another command does not take effect until the next line of input is read. The commands following the alias
definition on that line are not affected by the new alias. This behavior is also an issue when functions are executed. Aliases are expanded when a function definition is read, not when the function
is executed, because a function definition is itself a command. As a consequence, aliases defined in
a function are not available until after that function is executed. To be safe, always put alias
definitions on a separate line, and do not use alias in compound commands.
For almost every purpose, aliases are superseded by shell functions.

How do you load bash_profile for bash commands run from perl script?

I wrote simple command that lets me run the last N commands from terminal history. It looks like this: $> r 3 which will replay the last 3 commands.
I have the following alias in my bash profile:
alias r="history -w; runlast $1"
And then the following simple perl script for the runlast command:
#!/usr/bin/env perl
use strict;
use warnings;
my $lines = $ARGV[0] || exit;
my #last_commands = split /\n/,
`bash -ic 'set -o history; history' | tail -$lines`;
#last_commands =
grep { $_ !~ /(^r |^history |^rm )/ }
map { local $_ = $_; s/^\s+\d+\s+//; $_ }
#last_commands;
foreach my $cmd (#last_commands) {
system("$cmd");
}
This works but my bash profile has aliases and other features (e.g. color output) I want the perl script to have access to. How do I load the bash profile for perl so it runs the bash commands with my bash profile? I read somewhere that if you "source the bash profile" for perl you can get it to work. So I tried adding source ~/.bash_profile; to my r command alias but that didn't have an effect. I'm not sure if I was doing that correctly, though.
The system forks a process in which it runs a shell, which is non-login and non-interactive; so no initialization is done and you get no aliases. Also note that the shell used is /bin/sh, which is generally a link to another shell. This is often bash but not always, so run bash explicitly.
To circumvent this you need to source the file with aliases, but as bash man page says
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).
Thus you need shopt -s expand_aliases, as mentioned. But there is another screw: on that same physical line aliases are not yet available; so it won't work like this in a one-liner.
I'd also recommend to put aliases in .bashrc, or in a separate file that is sourced.
Solutions
Add shopt -s expand_aliases to your ~/.bashrc, and before the aliases are defined (or the file with them sourced), and run bash as a login shell
system('/bin/bash', '-cl', 'source ~/.bashrc; command');
where -l is short for --login.
In my tests the source ~/.bashrc wasn't needed; however, the man page says
When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.
and goes on to specify that ~/.bashrc is read when an interactive shel that is not login runs. So I added explicit sourcing.
In my tests sourcing .bashrc (with shopt added) while not running as a login shell didn't work, and I am not sure why.
This is a little heavy-handed. Also, initialization may be undesirable to run from a script.
Source ~/.bashrc and issue shopt command, and then a newline before the command
system('/bin/bash', '-c',
'source ~/.bashrc; shopt -s expand_aliases\ncommand');
Really. It works.
Finally, is this necessary? It asks for trouble, and there is probably a better design.
Other comments
The backticks (qx) is context-aware. If it's used in list context – its return assigned to an array, for example – then the command's output is returned as a list of lines. When you use it as the argument for split then it is in the scalar context though, when all output is returned in one string. Just drop split
my #last_commands = `bash -ic 'set -o history; history $lines`;
where I also use history N to get last N lines. In this case the newlines stay.
history N returns last N lines of history so there is no need to pipe to last
Regex substitution in a map can be done without changing the original
map { s/^\s+\d+\s+//r } #last_commands;
With /r modifier the s/// operator returns the new string, not changing the original. This "non-destructive substitution" has been available since v5.14
No need to explicitly use $_ in the last grep, and no need for parenthesis in regex
grep { not /^r |^history |^rm ?/ } ...
or
grep { not /^(?:r|history|rm)[ ]?/ } ...
where parens are now needed, but as it is only for grouping the ?: makes it not capture the match. I use [ ] to emphasize that that space is intended; this is not necessary.
I also added ? to make space optional since history (and r?) may have no space.
The proper solution is to have your Perl script just print the commands, and make your current interactive shell eval the string printed from your history. (I would probably get rid of Perl entirely but that's beside the point here.)
If the commands get evaluated in the current shell, you avoid many contextual problems which would be very hard or even intractable with system() or generally anything involving a new process. For example, a subprocess cannot have access to non-exported variables in the current shell. var="foo", echo "$var"; r 1 is going to be very hard to solve correctly with your current approach. Using the current interactive shell will also naturally and easily solve the problems you were having with trying to get a noninteractive subshell act like an interactive one.
Aliases suck anyway, so let's redefine r as a function:
r(){
history -w
eval $(printlast "$1")
}
... where refactoring runlast into a different script printlast is a trivial additional requirement. Or maybe just turn it into a (much simpler!) shell function:
printlast () {
history "$1" |
perl -ne 's/^\s*\d+\s+\*?//; print unless m/^(history|rm?)($|\s)'
}
With this, you can also get rid of history -w from the r definition.
Notice how we are using Perl where it is useful; but the main functionality makes sense to keep in the shell when you're dealing with the shell.
You can't source in a Bash script into a Perl script. The bash_profile has to be sourced in by the shell that executes the command. When Perl runs system, it forks a new shell each time.
You have to source in the bash_profile for each command that you run through system:
system('source ~/.bash_profile; ' + $cmd);
One more thing, system invokes a non-interactive shell. So, your Bash aliases defined in .bash_profile won't work unless you invoke:
shopt -s expand_aliases
inside that script

Bash: declaration of an alias to be used in a single lined multiple commands

I would like to do something like
alias myls=ls && myls
This gives me an error
bash: myls: command not found
Strangely, a subsequent
myls
in the same session would be recognized.
Do you know how to declare alias, that is used in the same command sequence?
According to the bash manual:
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. Therefore, an alias definition appearing on the same line
as another command does not take effect until the next line of input is read.
The commands following the alias definition on that line are not affected by the
new alias. This behavior is also an issue when functions are executed. Aliases
are expanded when a function definition is read, not when the function is executed,
because a function definition is itself a compound command. As a consequence,
aliases defined in a function are not available until after that function is executed.
To be safe, always put alias definitions on a separate line,
and do not use alias in compound commands.
For almost every purpose, aliases are superseded by shell functions.
alias has to be used on a separate line so even this won't work:
alias myls=ls; myls
This will result in -bash: myls: command not found error.
However, you can use function and use it on same line:
myls() { ls "$#"; } && myls
anubhava has already provided a better answer, but if for some reason you don't want to use a function instead of an alias then you can do
alias myls=ls && eval myls
Note, however, that usage of eval introduces an extra level of expansion, which may result in unexpected behaviour for more complex commands:
$ echo "A B"
A B
$ eval echo "A B"
A B
$ echo '$SHELL'
$SHELL
$ eval echo '$SHELL'
/bin/bash

How do create an alias in shell scripts? [duplicate]

This question already has answers here:
using alias in shell script? [duplicate]
(7 answers)
Closed 2 years ago.
Definin an alias on Linux system is very simple.
From the following example we see that: the I_am_only_ls_alias alias command gives us the output as ls command
# alias I_am_only_ls_alias=ls
# I_am_only_ls_alias
Output:
file file1
But when I trying to do the same in bash script (define alias I_am_only_ls_alias), I get I_am_only_ls_alias: command not found.
Example of my bash script:
alias_test.bash
#!/bin/bash
alias I_am_only_ls_alias=ls
I_am_only_ls_alias
Run the bash script - alias_test.bash
/tmp/alias_test.bash
Output:
/tmp/: line 88: I_am_only_ls_alias: command not found
So, first I want to ask:
Why doesn't bash recognize the command I_am_only_ls_alias as an alias?
And what do I need to do in order to define aliases inside a bash script? Is it possible?
From the bash man page:
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 this should work:
#!/bin/bash
shopt -s expand_aliases
alias I_am_only_ls_alias=ls
I_am_only_ls_alias
Scripts usually use functions, not aliases.
Barmar's answer is the correct one for including an alias, but it's likely that you'll actually find a Bash function to be more flexible/useful.
For example this is materially the same as the alias version, but can be expanded much more easily:
timp#helez:~/tmp$ cat test.sh
#!/usr/bin/bash
function i_am_only_an_ls_func {
ls "$#"
}
i_am_only_an_ls_func
timp#helez:~/tmp$ ./test.sh
0600871h.html
[snip]
timp#helez:~/tmp$
The $# is irrelavent in this example, but it means that anything after i_am_only_an_ls_func will be added after the ls command, since $#, $1, $2, etc contain the arguments to the function, much the same as for a normal script. (Note that $0 is still the name of the parent script not the function)
Aliases cannot be defined in shell script that you execute - their effect will be gone once shell process finished execution.
You can, however, define aliases in your ~/.bashrc or in separate shell script that you source from (but not execute!). In that case, aliases are imported into already running shell process, and thus survive and actually work as you would expect.

Execute a passed alias inside a function?

Background:
I'm trying make a function that runs commands on a set interval because I don't have access to a "watch" program. Simplified to it's most basic from, the function I'm trying to write is runit() { $1; }.
What works:
This works fine and dandy when I pass it things that aren't aliases. For example, runit "ls -l" works fine. I get the full output from the ls -l command.
What doesn't work:
The problem starts when I pass it an alias. For example, setting alias ll="ls -l" then calling runit "ll" will result in -bash: ll: command not found.
Things I have tried:
When I hard-code the alias runit() { ll; }, it works fine and gives me what I expect.
I feel like I might be overlooking something, but I can't quite place my finger on it.
Why would hard-coding the alias work fine, but passing it into the function fail?
Is there a way to accomplish what I'm attempting to do?
From the bash man page discussion of aliases (emphases mine):
Aliases are expanded when a command is read, not when it is executed.
Therefore, an
alias definition appearing on the same line as another command does not take effect until the next line of input is read. The
commands
following the alias definition on that line are not affected by the new alias. This behavior is also an issue when functions are
executed. Aliases are expanded when a function definition is read, not when the function is executed, because a function
definition is
itself a compound command. As a consequence, aliases defined in a function are not available until after that function is executed.
To
be safe, always put alias definitions on a separate line, and do not use alias in compound commands.
You can observe this effect in functions by using the type command:
$ run_it () { ll; }
$ type run_it
You should see that the body of the function contains a call to ls -l, not ll.
The last sentence of the section on aliases:
For almost every purpose, aliases are superseded by shell functions.
My interpretation of that line is: if you think you want to use an alias, try writing a function first. Don't use an alias unless the function demonstrably fails to do what you need.
You can use eval like this:
$ runit() { eval $1; }
$ alias ll="ls -l"
$ runit "ll"
eval will expand any alias in $1 before the execution.
One way to solve this problem is to define a shell function rather than an alias.
ll () {
ls -l "$#"
}
The alias is expanded as a macro on command input, whereas the shell function is matched when the command is executed. This is a perfect example of how the shell's macro processor language is good for interactive grace but rather complicates actual programming.

Resources