How does RVM gets invoked while going into a folder with Gemfile? - ruby

I am surprised at how RVM switches Ruby versions honouring Gemfile just by navigating into the directory via command-line? Is RVM getting a call back through the shell? Can anyone provide a pointer on this?
For instance message like this:
RVM used your Gemfile for selecting Ruby, it is all fine - Heroku does that too,
you can ignore these warnings with 'rvm rvmrc warning ignore /directory/path/to/Gemfile'.
To ignore the warning for all files run 'rvm rvmrc warning ignore allGemfiles'.

According to this post:
https://unix.stackexchange.com/questions/21363/execute-bash-scripts-on-entering-a-directory
rvm redefines the cd command. If I do what Amadan suggested, I get:
~$ type cd
cd is a function
cd ()
{
__zsh_like_cd cd "$#"
}
That looks like some kind of alias, so let's try:
~$ type __zsh_like_cd
__zsh_like_cd is a function
__zsh_like_cd ()
{
typeset __zsh_like_cd_hook;
if builtin "$#"; then
shift || true;
for __zsh_like_cd_hook in chpwd "${chpwd_functions[#]}";
do
if typeset -f "$__zsh_like_cd_hook" > /dev/null 2>&1; then
"$__zsh_like_cd_hook" "$#" || break;
fi;
done;
true;
else
return $?;
fi
}
In a zsh shell, chpwd is a hook function that is called when the current directory changes. But, I'm not sure why that works in a bash shell, which doesn't provide the chpwd hook function. Amadan?

Normally cd is a shell builtin. rvm defines cd function which gets invoked instead. Do this to see for yourself:
$ type cd
Then try it in a "clean" shell, to see the difference:
$ env -i PATH=$PATH HOME=$HOME bash -c "type cd"

Related

Automatically conda activate when in directory and source/ export

Goal: Automatically execute bash commands if when in directory.
For example, if I enter a git project directory, I'd like bash to run the following for me:
conda activate
export VAR_NAME=foo
I attempted by appending to ~/.bashrc, but with no luck:
...
if [ -f "/home/me/PycharmProjects/project/" ]; then
conda activate project_venv
export KEY=foo
export SECRET=bar
fi
You can set PROMPT_COMMAND, see the bash docs. Bash executes its value (or if it's an array, each of its values) before every prompt, so you can do whatever you want when PWD changes.
You can add this function to your ~/.bashrc:
cd () {
command cd "$#" &&
if [[ $(pwd) = '/home/me/PycharmProjects/project' ]]; then
conda activate project_venv
export KEY=foo SECRET=bar
fi
}
Because you are exporting in a function you need to use declare -gx
declare --help will give you the best and most accurate reason why but it is because all function vars are technically local. The -g create a global exported var for a function is ignored if not in a function and the -x is what export is an alias for. export is just declare -x. You will also need to source your script files
So it will look like this
declare -gx KEY=foo
declare -gx SECRET=bar
cd () {
command cd "$#" &&
if [[ $(pwd) = '/home/me/PycharmProjects/project1' ]]; then
conda activate project1
source ~/miniconda3/etc/activate.d/env_vars.sh
elif [[ $(pwd) = '/home/me/PycharmProjects/project2' ]]; then
conda activate project2
else
source ~/miniconda3/etc/deactivate.d/env_vars.sh
fi
}
Full disclosure I'm not sure if the -x is completely necessary but I do it in case of sourcing a script.
Also storing in secrets in ~/.bashrc is a general no no as it leads to bad actors getting secrets. Ontop of slowing down your interactive shell loading times

How to run a function when the cd command is run

I am kind of new to Linux and am just learning to make the computer do the work that I want.
So my wish is that whenever I use the cd
command, I want it to change directory and then list all the files present in them. If there are no arguments passed, I want the pwd command to run.
This is what I have done so far.
function cd {
if [ $# -eq 0 ]
then
pwd
else
cd "$1"; ls -l
fi
}
When I run this, it works fine when there are no arguments passed and it runs the pwd command. However, when I pass an argument, it does not display anything and it closes the terminal, which is'nt what I want.
When I changed the function name to ca though, and ran ca, it worked as expected.
Why is this so? Are there a list of aliases I am not allowed to use? How can I make it work?
If it exists, chpwd_functions is an array of function names, each of which will be called, in order, whenever the working directory changes. In your case, it could be used as follows:
foo () {
if [[ $PWD == $HOME ]]; then
pwd
else
ls -l
fi
}
chpwd_functions+=(foo)
You recursively call your function instead of calling the cd builtin.
In ZSH the builtin command can be used to execute a builtin explicitly suppressing shell function lookup. This is exactly what you need to implement a function that has the same name as a shell builtin
function cd {
if [ $# -eq 0 ]
then
pwd
else
builtin cd "$1" ; ls -l
fi
}
This applies to BASH as well.
Regarding the command builtin
In BASH you could use the command builtin to execute an external command or a builtin. That is where BASH is different from ZSH as in ZSH command executes external commands only.
Only in BASH
command cd works the same as builtin cd (assuming /bin/cd does not exist)
In ZSH
command cd would probably fail with cd: command not found unless /bin/cd exists
The "cd" command in Bash can take options, so using just $1 will drop the directory name. This passes everything:
function cd {
if [ $# -eq 0 ]
then
pwd
else
cd "$#"; ls -l
fi
}
I was having a similar issue having switched from bash to zsh. Example function...
go() {
if [[ $# == "code" ]]; then command cd "$CODE";
}
The solution for me was to 1. prefix my function with function and 2. replace command with builtin
function go() {
if [[ $# == "code" ]]; then builtin cd "$CODE";
}

Customize "command not found" message in Bash

Is there someway to alter the Bash system error message template so that you can print something in addition to the original message? For example:
Macbook Air:~/Public]$ lfe
-bash: lfe: WTF command not found
or
Macbook Air:~/Public]$ lfe
-bash: lfe: #!&**! command not found
Since Bash 4.0, if the search for a command is unsuccessful, the shell searches for a function called command_not_found_handle. If it doesn't exist, Bash prints a message like this and exits with status 127:
$ foo
-bash: foo: command not found
$ echo $?
127
If it does exist, it is called with the command and its arguments as arguments, so if you have something like
command_not_found_handle () {
echo "It's my handle!"
echo "Arguments: $#"
}
in your .bashrc, Bash will react like this:
$ foo bar
It's my handle!
Arguments: foo bar
Most systems have something much more sophisticated in place, though. My Ubuntu, for example, has this in /etc/bash.bashrc:
# if the command-not-found package is installed, use it
if [ -x /usr/lib/command-not-found -o -x /usr/share/command-not-found/command-not-found ]; then
function command_not_found_handle {
# check because c-n-f could've been removed in the meantime
if [ -x /usr/lib/command-not-found ]; then
/usr/lib/command-not-found -- "$1"
return $?
elif [ -x /usr/share/command-not-found/command-not-found ]; then
/usr/share/command-not-found/command-not-found -- "$1"
return $?
else
printf "%s: command not found\n" "$1" >&2
return 127
fi
}
fi
and this is sourced from /etc/profile. /usr/lib/command-not-found is a Python script that uses some more Python (CommandNotFound) to basically look up packages that are named like the unknown command, or sound similar:
$ sl
The program 'sl' is currently not installed. You can install it by typing:
sudo apt install sl
$ sedd
No command 'sedd' found, did you mean:
Command 'sed' from package 'sed' (main)
Command 'seedd' from package 'bit-babbler' (universe)
Command 'send' from package 'nmh' (universe)
Command 'send' from package 'mailutils-mh' (universe)
sedd: command not found
So if you want simple customization, you can provide your own command_not_found_handle, and if you want to customize the existing system, you can modify the Python scripts.
But, as mentioned, this requires Bash 4.0 or higher.
Maybe something like:
curl https://raw.githubusercontent.com/rcaloras/bash-preexec/master/bash-preexec.sh -o ~/.bash-preexec.sh
echo '[[ -f ~/.bash-preexec.sh ]] && source ~/.bash-preexec.sh' >> ~/.bashrc
then add the following to .bashrc too
preexec() { type "$1" >/dev/null 2>&1 || echo -n 'WTF??? '; }
reload your shell, then try enter some nonexistent command, like bububu
$ bububu
will print
WTF??? -bash: bububu: command not found
Important: read https://github.com/rcaloras/bash-preexec

Echo directory after change?

(Since I couldn't find an explicit answer to this anywhere and I find it useful, I though I would add it to SO. Better alternatives welcome.)
In Bash, how do you alias cd to echo the new working directory after a change? Like this:
$ pwd
/some/directory
$ cd newness
/some/directory/newness
$
Something simple like alias cd='cd "$#" && pwd' doesn't work. For some reason, Bash responds as though you used cd - and returns to $OLDPWD and you get caught in a loop. I don't understand this behavior.
Apparently, you need to do this through a function:
function mycd() {
cd "$#" && pwd
}
alias cd=mycd
However, if you use cd - you wind up printing the directory twice, so this is more robust:
function mycd() {
if [ "$1" == "-" ]; then
cd "$#"
else
cd "$#" && pwd
fi
}
alias cd=mycd
I may be missing some edge cases, like cd -P - and cd -L -, though I don't know if those even make sense.
(See Adrian Frühwirth's answer below for the reason why a simple alias doesn't work and why I feel dumb now.)
The alias doesn't work because aliases don't support arguments, so you cannot chain commands that require parameters. Because of this cd "$#" doesn't make sense in the context of an alias and whatever argument you supply to an alias just gets appended:
$ alias foo='echo "[$#]" && echo'
$ foo bar
[]
bar
Obviously, that's not what you want and exactly why you need to resort to a function.

customizing cd command

Generally I keep directory specific settings in .bashrc and whenever I change directory execute the command source .bashrc to make those settings effective.
Now I was thinking of manipulating cd command in ~/.bashrc, so whenever I cd to new directory and if any .bashrc exists there, it will be loaded automatically.
Similar to this cd $1; source .bashrc ( I have verified that $1 is valid path), but problem is cd is shell builting, so it's a recursive loop ( cd always points to modifed cd ). We do not have elf file of cd ( which generally we have of other commands viz scp or others). So how can I achieve this ?
Also if shopt -s cdspell is supported then also I need to have cd spelled path in argument of $1.
You want the "builtin" command;
builtin shell-builtin [arguments]
Execute the specified shell builtin,
passing it arguments, and return its exit status. This is useful when
defining a function whose name is the same as a shell builtin,
retaining the functionality of the builtin within the function. The cd
builtin is commonly redefined this way. The return status is false if
shell-builtin is not a shell builtin command.
From: http://linux.die.net/man/1/bash
So, you could have something like (untested, don't have a bash handy either);
function cd() {
builtin cd $1 \
&& test -e .bashrc \
&& source .bashrc
}
You might check out direnv. https://github.com/zimbatm/direnv
RVM does this:
$ type cd
cd is a function
cd ()
{
if builtin cd "$#"; then
[[ -n "${rvm_current_rvmrc:-}" && "$*" == "." ]] && rvm_current_rvmrc="" || true;
__rvm_do_with_env_before;
__rvm_project_rvmrc;
__rvm_after_cd;
__rvm_do_with_env_after;
return 0;
else
return $?;
fi
}
And yes, this works on my machine. Essentially, as #RoryHunter said, use builtin and run some code if it succeeds, or return the exit code if it fails.
You could try this:
function cdd(){ cd $1; if [ -e ./.bashrc ] ; then source ./.bashrc; fi; }
alias cd = 'cdd'
?
Didn't tested this much, however.

Resources