How to "hide" an executable from a bash script? - bash

I want to test the output of a bash script when one of the executables it depends on is missing, so I want to run that script with the dependency "hidden" but no others. PATH= ./script isn't an option because the script needs to run other executables before it reaches the statement I want to test. Is there a way of "hiding" an executable from a script without altering the filesystem?
For a concrete example, I want to run this script but hide the git executable (which is its main dependency) from it so that I can test its output under these conditions.

You can use the builtin command, hash:
hash [-r] [-p filename] [-dt] [name]
Each time hash is invoked, it remembers the full pathnames of the commands specified as name arguments, so they need not be searched for on subsequent invocations. ... The -p option inhibits the path search, and filename is used as the location of name. ... The -d option causes the shell to forget the remembered location of each name.
By passing a non-existent file to the -p option, it will be as if the command can't be found (although it can still be accessed by the full path). Passing -d undoes the effect.
$ hash -p /dev/null/git git
$ git --version
bash: /dev/null/git: command not found
$ /usr/bin/git --version
git version 1.9.5
$ hash -d git
$ git --version
git version 1.9.5

Add a function named git
git() { false; }
That will "hide" the git command
To copy #npostavs's idea, you can still get to the "real" git with the command builtin:
command git --version

Since we know the program is running in bash, one solution is to - instead of "hiding" the program - emulate the behaviour of bash in this circumstance. We can find out what bash does when a command isn't found quite easily:
$ bash
$ not-a-command > stdout 2> stderr
$ echo $?
127
$ cat stdout
$ cat stderr
bash: not-a-command: command not found
We can then write this behaviour to a script with the executable name, such as git in the question's example:
$ echo 'echo >&2 "bash: git: command not found" && exit 127' > git
$ chmod +x git
$ PATH="$PWD:$PATH" git
$ echo $?
127
$ cat stdout
$ cat stderr
bash: git: command not found

Related

retrieve the path of a repo from the "git bash" from windows with right format

I am trying to retrieve the path of a repository from the "git bash" shell in windows, as follows
user#CND7293ZVV MINGW64 /c/Work/git/repository/subfolder (master)
$ git rev-parse --show-toplevel
C:/Work/git/repository
The problem is that I want to use the ouptut path to use it from a bash script, but it is not in bash format. I want to get /c/Work/git/repository. Is there any way to get the path in a way that can be used directly in the git bash shell?
extra information: The target is to store that path in a variable to be used inside a bash script, independently of whether I am running bash from a linux terminal, or when I am running from the git bash.
Update:
To be able to use the command inside the git-bash environment, and also inside native linux bash, we can use the following:
REPODIR=$(git rev-parse --show-toplevel)
# If we are inside mingw* environment, then we update the path to proper format
if [[ $(uname) == MINGW* ]] ; then REPODIR=$(cygpath -u "${REPODIR}"); fi; echo ${REPODIR}
When running on the Git bash command line, you have an buildin utility with the name cygpath that does this:
$ cygpath -u "C:/Work/git/repository"
/c/Work/git/repository
This can be combined with your existing command to do everything on 1 line:
$ cygpath -u "$(git rev-parse --show-toplevel)"
/c/Work/git/repository

copying bash completion (without copying the actual code)

Suppose I have a command git-local (it could be a Bash function or a binary in /usr/local/bin) and suppose I would like git-local to have the same tab completion as the command git has. Finally, suppose that I'm efficient (read lazy) and I don't want to go find the code that the git commmand uses to manually copy over and bloat my .bashrc (or whatever external file I paste it in and the source). Is there a simple way I can have git-local use the same autocompletion as git?
8.7 Programmable Completion Builtins:
If the -p option is supplied, or if no options are supplied, existing completion specifications are printed in a way that allows them to be reused as input.
Something like
$(complete -p git | awk '$NF="git-local"')
maybe?
E.g.:
$ complete -p foobar
-bash: complete: foobar: no completion specification
$ complete -p traceroute
complete -F _known_hosts traceroute
$ $(complete -p traceroute | awk '$NF="foobar"')
$ complete -p foobar
complete -F _known_hosts foobar

cat a file to a variable

Assuming the name of my script is myscript.sh and my current directory is /Users/scripts/ I'm trying to do the following:
localScript=$(cat ./myscript.sh)
I get the following error:
#!/bin/sh not found
I can't seem to figure out how to do this, but I assume its not working because $() is creating a subshell that has a different pwd and thus cannot find my file.
I've also tried using various combinations of pwd but I'm having trouble with this method as well.
On OSX I've done the following:
$ vim test.sh
and typed in the following:
#!/bin/sh
localScript=$(cat ./test.sh)
echo $localScript
and then,
$ chmod +x test.sh
$ ./test.sh
which gives the following output:
#!/bin/sh localScript=$(cat ./test.sh) echo $localScript
Maybe the above will help you spot your error.

About builtin command puzzle

[root#MGWSDT_FEWS bin]# type cd
cd is a shell builtin
[root#MGWSDT_FEWS bin]# which cd
/usr/bin/which: no cd in (.:/usr/expect/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/real/RealPlayer:/root/bin)
It says that because cd is a builtin command, so 'which cd' failed.
'pwd' is also a builtin command, why 'which pwd' can get its location?
[root#MGWSDT_FEWS ~]# type pwd
pwd is a shell builtin
[root#MGWSDT_FEWS ~]# which pwd
/bin/pwd
Understand that for sudo or any other command that is not a shell builtin, when the command terminates, the shell picks up where it left off before the command was run (of course, the shell's current working directory won't change). As such, cd must be a shell builtin; otherwise, when cd terminates, the shell is back to the same old current working directory. In other words, a child process (i.e. an external command) can't change the working directory of the parent process (i.e. the shell).
Courtesy: https://bbs.archlinux.org/viewtopic.php?id=127225
cd command is bash's command. (bash built in command)
You can find cd's usage in "man bash & find cd"
Contrary, pwd is standalone command.
Builtin commands are internal commands in the shell and has a higher priority over external commands (files). And builtin commands also have lower priority than functions so builtin is actually helpful if you have a function with the same name as the builtin command like this:
function cd {
if [[ $# -gt 0 ]]; then
echo "Changing directory to $1."
builtin cd "$1"
else
echo "Changing to default directory."
builtin cd
fi
}
cd "/some/where"
As a summary functions gets called first before builtins, and builtins gets called first before binary commands or files. The type command I think would also follow that order when interpreting an argument.
There are several instances where "commands" are duplicated, but of course this will depend on your installation. If you have a shell built-in called pwd then that will be used, but there might be shells (csh) that do not have pwd as a built-in.
printf is a shell built-in for bash and ksh93 but not for ksh88 or csh.
Another (weirder) example is [, which is a shell built-in but (on some systems) a symbolic link to test (which might also be a shell built-in).
Your installation is there to support many shells and other programs, not just bash.
cd has to be shell-built-in, because it were a separate process, then the effect of the command would have vanished, after the command exits. (See Sakthi Kumar's answer.)
On the other hand, some other commands like pwd, test are available as binaries, like /bin/pwd, /usr/bin/test... However, for optimization, shell has also implementation of these binaries within /bin/bash itself, so that the overhead of creating a separate process is avoided.
when you call pwd, shell calls its own pwd implementation. You can call the binary /bin/pwd using command pwd instead of just pwd.
Commands such as pwd and echo are bash-builtins and also available as external commands. You can use enable to enable/disable shell builtins.
The following example iillustrates how pwd can be invoked either as a shell-builtin or as external command i.e. /bin/pwd.
$ type pwd
pwd is a shell builtin
$ which pwd
/bin/pwd
$ enable -n pwd
$ type pwd
pwd is /bin/pwd
$ which pwd
/bin/pwd
$ enable pwd
$ type pwd
pwd is a shell builtin
$ which pwd
/bin/pwd

Shell/Bash - pipe output into another script's input via a variable

Normally I would break things into separate actions and copy and paste the output into another input:
$ which git
/usr/local/bin/git
$ sudo mv git-credential-osxkeychain /usr/local/bin/git
Any quick hack to get output into input?
something like:
$echo which wget | sudo mv git-credential-osxkeychain
set -vx
myGit=$(which git)
gitDir=${myGit#/git} ; gitDir=${gitDir#/bin}/git
echo sudo mv git-credential-osxkeychain ${gitDir}
Remove the set -vx and the echo on the last line when you're sure this performs the action that you require.
It's probably possible to reduce the number of keystrokes required, but I think this version is easier to understand what techniques are being used, and how they work.
IHTH
use command substitution with $(command)
sudo mv git-credential-osxkeychain $(which git)
This substitutes the command for its output. You can find all about it in http://tldp.org/LDP/abs/html/commandsub.html
The answer would be what Chirlo and shellter said.
Why $echo which wget | sudo mv git-credential-osxkeychain wouldn't work is because piping redirect the stdout from a previous command to the stdin of the next command. In this case, move doesn't take input from stdin.
A curious thing is that which git returns
/usr/local/bin/git
but you are moving git-credential-osxkeychain to
/usr/local/git/bin/
Those two don't match. Is there a typo or something?
If you want to use the pipe syntax, then you should look at xargs.

Resources