Override of "select" builtin - bash

Recently I tried to override three common commands:
sleep
wait
select
The first one (sleep) is commonly an external bin (/bin/sleep in my Debian 10).
The second one (wait) is a builtin (You can check it with command -v wait).
The third one (select) is also a builtin.
I will write some examples in order to reproduce what works and what not, please be patient.
There are no problems in overriding sleep and wait, I just added somewhere in my code the following:
sleep() {
echo using custom sleep
}
wait() {
echo using custom wait
}
Things change when I try to override select.
In particular, if I try to create a simple function as above, I get errors as the parser (?) thinks I am trying to use the command rather than create a new function.
You can reproduce the error with the following:
#!/bin/bash
select() {
echo using custom select
}
This is avoidable using the notation function select() {.
If I'm using an interactive shell I can solve the issue with an alias; steps to reproduce:
_select() { echo using custom select;}
alias select='_select'
select
BUT this solution doesn't work if I use files.
Let's try something like:
#!/bin/bash
# this is the actual script
. lib.sh
select
... and:
#!/bin/bash
# this is where I declare the function select
_select() {
echo using custom select
}
alias select='_select'
If I run script which in turn source lib.sh I will get an error when I try to call my alias.
This is the first time, for me, that an alias is a "second choice" after a builtin.
Is there something I am doing wrong or is this an actual "bug"?
Obviusly, a workaround would be rename the function in something else.

From the bash man page in section ALIASES:
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).
Either add
shopt -s expand_aliases
before the first use of select or add -i to your shebang
#!/bin/bash -i
for an interactive shell.

Related

replace rm with gio trash [duplicate]

I am trying to use the solution of using sudo on my existing aliases as covered in many existing answers already and i am so confused as to why it is not working
alias sudo='sudo '
I keep all my aliases in .bash_aliases. Inside .bash_aliases I have this
function _test {
echo 'test!'
}
alias test='_test'
I reload the .bashrc each time; source .bashrc but when I run sudo test I always get
sudo: _test: command not found
The only strange thing that happens is that I get the following on reload and not on a new terminal
dircolors: /home/MYHOME/.dircolors: No such file or directory
but i feel this is a red herring.
As l0b0 says, aliases cannot be used in this way in bash.
However, you can pass a function through (and really, there's basically never a good reason to use an alias instead of sticking to functions alone).
_test() {
echo 'test!'
}
sudo_test() {
sudo bash -c "$(declare -f _test)"'; _test "$#"' sudo_test "$#"
}
...will define a command sudo_test that runs the function _test via sudo. (If your real-world version of this function calls other functions or requires access to shell variables, add those other functions to the declare -f command line, and/or add a declare -p inside the same command substitution to generate a textual description of variables your function needs).
To run an alias like alias_name it must be exactly the first word in the command, so sudo alias_name will never work. Ditto 'alias_name', \alias_name and other things which eventually expand to the alias name.

bash alias using sudo returning command not found

I am trying to use the solution of using sudo on my existing aliases as covered in many existing answers already and i am so confused as to why it is not working
alias sudo='sudo '
I keep all my aliases in .bash_aliases. Inside .bash_aliases I have this
function _test {
echo 'test!'
}
alias test='_test'
I reload the .bashrc each time; source .bashrc but when I run sudo test I always get
sudo: _test: command not found
The only strange thing that happens is that I get the following on reload and not on a new terminal
dircolors: /home/MYHOME/.dircolors: No such file or directory
but i feel this is a red herring.
As l0b0 says, aliases cannot be used in this way in bash.
However, you can pass a function through (and really, there's basically never a good reason to use an alias instead of sticking to functions alone).
_test() {
echo 'test!'
}
sudo_test() {
sudo bash -c "$(declare -f _test)"'; _test "$#"' sudo_test "$#"
}
...will define a command sudo_test that runs the function _test via sudo. (If your real-world version of this function calls other functions or requires access to shell variables, add those other functions to the declare -f command line, and/or add a declare -p inside the same command substitution to generate a textual description of variables your function needs).
To run an alias like alias_name it must be exactly the first word in the command, so sudo alias_name will never work. Ditto 'alias_name', \alias_name and other things which eventually expand to the alias name.

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

Create an alias of the script by calling the script with an argument: ./myscript alias

I would like to be able to create an alias of my script by calling it with an argument.
$ ./devbox alias
$ devbox <other command of my script>
Here is the code of my bash script:
#!/bin/bash
shopt -s expand_aliases
aliasDevbox()
{
alias devbox="./devbox"
}
parseCli()
{
command=$1
case "$command" in
"alias") aliasDevbox
;;
esac
}
parseCli "$#"
It's actually not working: command not found
(I know that if my code alias devbox="./devbox" was at the beginning of my script, I would be able to call it this way source ./devbox and it would work.)
Thank you!
This won't and can't work because ./devbox is a child process of your shell, and child processes can't set aliases in parent shells. (Similarly, they can't change environment variables either.)
I can suggest workarounds, but taking a step back, is this really needed? Typically, users will set up aliases in a login script such as .bashrc so that the aliases are automatically available in any new shell they start. My suggestion is simply to write some documentation suggesting that users create an alias for your tool to make it easier to use.

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