How to get builtin of the builtin command? - bash

Out of curiosity, say you created this function:
cd () { echo 'hi'; }
Now, whenever you entered cd you'd get "hi" printed. In case, you wanted to use the original cd command, you could do the following:
builtin cd
But what if I also created this:
builtin() { echo 'haha, got ya!'; }
Now, how do you get the builtin command for either builtin or cd?

Okay first off don't override the builtin command, which I assume you're not doing on purpose. Given the fact that you can override all the builtins, I thought up a more ridiculous route:
#!/bin/bash
builtin() { echo "Ha"; }
cd() { echo "Ha"; }
pwd() { echo "foobar"; }
result=$(/bin/bash --noprofile --norc -c "\\cd /home/cwgem/testdir; \\pwd; \\ls" )
/bin/echo "$result"
Result:
$ bash test.sh
/home/cwgem/testdir
test.txt
The idea here is to:
Use absolute paths to functions which can't be overridden as a function (this of course goes under the assumption that you don't go messing around with /bin/bash and /bin/echo)
Utilize the fact that $() is syntactic command substitution and I wasn't able to override that
Calling bash with --noprofile and --norc, which are explicit in not sourcing in the usual files which might contain overrides
Finally \'s are added in front of commands in case aliases are floating around
That said, all and all this hack was done out of academic curiosity and should not be applied in the real world.

I remembered about the unset command while writing this question down. If anyone else wants to know, you can do:
unset builtin
and
unset cd
to make the original commands work as expected. If anyone has a way to do so without un-setting the methods, I'd be interested in knowing how.

Another option would be to use env -i to strip off the environment with its aliases and functions:
builtin() { echo 'haha, got ya!'; }
builtin pwd
/usr/bin/env -i bash -c 'builtin pwd'

Related

How do I specify a default command-line argument for a different (Python) script via a shell script?

My understanding of shell is very minimal, I'm working on a small task and need some help.
I've been given a python script that can parse command line arguments. One of these arguments is called "-targetdir". When -targetdir is unspecified, it defaults to a /tmp/{USER} folder on the user's machine. I need to direct -targetdir to a specific filepath.
I effectively want to do something like this in my script:
set ${-targetdir, "filepath"}
So that the python script doesn't set a default value. Would anyone know how to do this? I also am not sure if I'm giving sufficient information, so please let me know if I'm being ambiguous.
I strongly suggest modifying the Python script to explicitly specify the desired default rather than engaging in this kind of hackery.
That said, some approaches:
Option A: Function Wrapper
Assuming that your Python script is called foobar, you can write a wrapper function like the following:
foobar() {
local arg found=0
for arg; do
[[ $arg = -targetdir ]] && { found=1; break; }
done
if (( found )); then
# call the real foobar command without any changes to its argument list
command foobar "$#"
else
# call the real foobar, with ''-targetdir filepath'' added to its argument list
command foobar -targetdir "filepath" "$#"
fi
}
If put in a user's .bashrc, any invocation of foobar from the user's interactive shell (assuming they're using bash) will be replaced with the above wrapper. Note that this doesn't impact other shells; export -f foobar will cause other instances of bash to honor the wrapper, but that isn't guaranteed to extend to instances of sh, as used by system() invocations, Python's Popen(..., shell=True), and other places in the system.
Option B: Shell Wrapper
Assume you rename the original foobar script to foobar.real. Then you can make foobar a wrapper, like the following:
#!/usr/bin/env bash
found=0
for arg; do
[[ $arg = -targetdir ]] && { found=1; break; }
done
if (( found )); then
exec foobar.real "$#"
else
exec foobar.real -targetdir "filepath" "$#"
fi
Using exec terminates the wrapper's execution, replacing it with foobar.real without remaining in memory.

How to evaluate bash function arguments as command with possible environment overrides?

How to write a function in bash (I can rely on it being v4+), that, given words constituting a command with possible environment overrides, execute this command in the current shell?
For example, given
f cd src
f CXX="ccache gcc" make -k XOPTIONS="--test1 --test2"
the function f would do approximately same thing as simply having these lines in the shell script without the f up front?
A few unsuccessful attempts.
This tries to evaluate environment override CXX="ccache gcc" as command.
f() { "$#" ; }
This loses word-quoting on all arguments, breaking single argument words on spaces:
f() { eval "$#" ; }
This handles the environment overrides, but runs the command in a subshell, as env(1) is not a bash builtin:
f() { env -- "$#" ; }
This question came up multiple times on SO and Unix SE, but I have never seen it asked about supporting all three important parts, namely: environment overrides; execution in the current shell; and correct handling of arguments containing spaces (and other characters that are lexically special to bash).
One thing I could potentially use is that environment overrides are rarely used with builtins (but v. IFS= read...), so I can select between the "#" ; and eval -- "#" ; patterns based on $1 being syntactically a variable assignment. But that is, again, not as simple as spotting a = in it, as the equal sign may be quoted part of a command, albeit that is not likely sane. Still, I usually prefer correct code to mostly correct code, and this approach has 2 consecutive leaps of faith.
Addressing a possible question why do I need a function replicating the default behavior of the shell ("just drop the f"): in reality, f() is more complex that just running a command, implementing a pattern repeating in the script in a few dozen locations; this is only the part I cannot get right.
If you can make eval see your arguments properly quoted, it should work. To this end, you can use the %q format specification of printf, which works as follows:
$ printf '%q ' CXX="ccache gcc" make -k XOPTIONS="--test1 --test2"
CXX=ccache\ gcc make -k XOPTIONS=--test1\ --test2
This would result in a function like
f () {
eval "$(printf '%q ' "$#")"
}
Notice that this appends an extra space at the end of the command, but this shouldn't hurt.
Tricky. You could do this, but it's going to pollute the environment of the shell:
f() {
# process any leading "var=value" assignments
while [[ $1 == ?*=* ]]; do
declare -x "$1"
shift
done
"$#"
}
Just did a quick test: the env vars declared in the function are still local to the scope of the function and will not actually pollute the script's environment.
$ f() {
declare -x FOO=bar
sh -c 'echo subshell FOO=$FOO'
echo function FOO=$FOO
}
$ unset foo
$ f
subshell FOO=bar
function FOO=bar
$ echo main shell FOO=$FOO
main shell FOO=

Passing an alias as a function parameter

Newcomer to bash scripting here. Been outfitting my bash_profile with some useful functions to query some mysql databases, but am having trouble getting bash to recognize a passed parameter as an alias. See below for details:
function findfield() {
$2 -e
"SELECT TABLE_NAME,TABLE_SCHEMA,COLUMN_NAME AS 'Matched Field'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME LIKE '$1';"
}
example usage:
findfield %field% mpc
Where mpc is an alias that points to the database to query. This usage returns an error:
-bash: mpc: command not found
The above function works if I simply hardcode mpc in place of $2--so why wouldn't it work with an alias as a parameter instead?
Aliases don't work by default in noninteractive shells. You can change that with shopt -s expand_aliases, but I'm not sure it will help.
You need another layer of evaluation. By the time bash finishes substituting everything and wants to run the command, it thinks of "mpc" as a string. You can fix this change that with eval, but then you need to safeguard the other parameters and what if someone passes something naughty? This is why the use of eval is generally frowned upon. Sometimes it is unavoidable though:
$ run() { $1; }
$ alias alal=uname
$ run whoami
lynx
$ run alal
bash: alal: command not found
$ run() { shopt -s expand_aliases; $1; shopt -u expand_aliases; }
$ run alal
bash: alal: command not found
$ run() { shopt -s expand_aliases; eval $1; shopt -u expand_aliases; }
$ run alal
Linux
Anyway, you also need to fix the quoting in the sql or the field will never get expanded. The syntax highlighting here makes this obvious. A simple way is just to enclose $1 in a pair of ", so you effectively split the string into three until it is passed on.
You may need to add an extra line in your bash_profile file:
function myalias_func()
{
some code here with different variables $1, $2...
}
alias myalias=myalias_func
That is, try including the line
alias findfield=findfield
and it should work then.

check last command

I want to execute some code every time cd is executed. I'm getting hung up on actually testing if cd was the last command but I think I'm on the right track. Here is what I've added to my bash_profile
update_prompt()
{
if [ 'cd' = $1 ]; then
#DO STUFF
fi
}
PROMPT_COMMAND="update_prompt "$(!:0)"; $PROMPT_COMMAND"
This is close but it tries to actually execute the command in $1 rather than treat it as a string. Any ideas?
Edit: this is the solution I came up with. It's not based on the question directly, but rather on #schwiz's explanations in the comments:
update_prompt()
{
LAST=$(history | tail -n 1 | cut -d \ -f 5)
if [ "cd" = "$LAST" ]; then
# DO STUFF
fi
}
PROMPT_COMMAND="update_prompt"
Answering original question: when comparing string variable put it in parentheses to avoid interpreting:
update_prompt()
{
if [ 'cd' = "$1" ]; then
# ...
fi
}
Optionally you can consider defining an alias for cd:
alias cd='echo executed && cd'
Here's a more verbose way of writing !:0 that seems to play better with PROMPT_COMMAND:
PROMPT_COMMAND='update_prompt $(history -p !:0); $PROMPT_COMMAND'.
The single quotes prevent the command substitution from happening until PROMPT_COMMAND is actually called. Technically, there is no history expansion happening; you are using the history command to process the !:0 string as an argument, which does however have the same effect as the intended history expansion.
I would write a shell function that wraps cd, rather than using PROMPT_COMMAND to check the last command executed:
cd () {
builtin cd "$#"
# CUSTOM CODE HERE
}

How to pass command line arguments to a shell alias? [duplicate]

This question already has answers here:
Make a Bash alias that takes a parameter?
(24 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question 12 months ago and left it closed:
Original close reason(s) were not resolved
How do I pass the command line arguments to an alias? Here is a sample:
alias mkcd='mkdir $1; cd $1;'
But in this case the $xx is getting translated at the alias creating time and not at runtime. I have, however, created a workaround using a shell function (after googling a little) like below:
function mkcd(){
mkdir $1
cd $1
}
Just wanted to know if there is a way to make aliases that accept CL parameters.
BTW - I use 'bash' as my default shell.
Just to reiterate what has been posted for other shells, in Bash the following works:
alias blah='function _blah(){ echo "First: $1"; echo "Second: $2"; };_blah'
Running the following:
blah one two
Gives the output below:
First: one
Second: two
You found the way: create a function instead of an alias. The C shell has a mechanism for doing arguments to aliases, but bash and the Korn shell don't, because the function mechanism is more flexible and offers the same capability.
You cannot in ksh, but you can in csh.
alias mkcd 'mkdir \!^; cd \!^1'
In ksh, function is the way to go. But if you really really wanted to use alias:
alias mkcd='_(){ mkdir $1; cd $1; }; _'
To quote the bash man page:
There is no mechanism for using arguments in the replacement text. If
arguments are needed, a shell function should be used (see FUNCTIONS
below).
So it looks like you've answered your own question -- use a function instead of an alias
You may also find this command useful:
mkdir dirname && cd $_
where dirname is the name of the directory you want to create
The easiest way, is to use function not alias. you can still call a function at any time from the cli. In bash, you can just add function name() { command } it loads the same as an alias.
function mkcd() { mkdir $1; cd $1 ;}
Not sure about other shells
I found that functions cannot be written in ~/.cshrc file. Here in alias which takes arguments
for example, arguments passed to 'find' command
alias fl "find . -name '\!:1'"
Ex: >fl abc
where abc is the argument passed as !:1
You actually can't do what you want with Bash aliases, since aliases are static. Instead, use the function you have created.
Look here for more information: http://www.mactips.org/archives/2008/01/01/increase-productivity-with-bash-aliases-and-functions/. (Yes I know it's mactips.org, but it's about Bash, so don't worry.)
This works in ksh:
$ alias -x mkcd="mkdir \$dirname; cd \$dirname;"
$ alias mkcd
mkcd='mkdir $dirname; cd $dirname;'
$ dirname=aaa
$ pwd
/tmp
$ mkcd
$ pwd
/tmp/aaa
The "-x" option make the alias "exported" - alias is visible in subshells.
And be aware of fact that aliases defined in a script are not visible in that script (because aliases are expanded when a script is loaded, not when a line is interpreted). This can be solved with executing another script file in same shell (using dot).
Here's a simple example function using Python. You can stick in ~/.bashrc.
You need to have a space after the first left curly bracket.
The python command needs to be in double quotes to get the variable substitution. Don't forget that semicolon at the end.
count(){ python -c "for num in xrange($1):print num";}
Example run:
$ count 6
0
1
2
3
4
5
$
An empty alias will execute its args:
alias DEBUG=

Resources