Is there a way of set the PATH variable exclusively for one executable in bash script?
I want to do so because somehow macOS's LLDB requires system-intalled Python, not my Anaconda-managed Python, therefore I need to ensure /usr/bin is at the beginning of PATH. But I prefer Anaconda-managed Python for everyday use, so I don't want to set PATH permanently just to accommodate LLDB.
Temporarily manually writing PATH before and after using LLDB is cumbersome, so I'm thinking about some kind of wrapper script or alias that automates this routine.
P.S. LLDB has the same problem with Homebrew-managed Python.
Environment variables are, by definition, per-process. Each process has a copy of the environment which it can modify for its own reasons.
To override the PATH just for a single invocation, all sh-compatible shells allow you to say
PATH=newvalue executable with arguments
which sets PATH to newvalue for the duration of the execution of executable with arguments, then reverts the value back to its previous state (the current value, or unset if it was unset).
If you want to override something in the environment every time you execute something, you need a wrapper. Assuming you have /usr/local/bin before /usr/bin in your PATH, you could install this in /usr/local/bin/something to override /usr/bin/something with a wrapper:
#!/bin/sh
PATH=newvalue
exec /usr/bin/something "$#"
Remember chmod a+x and of course you need to be root to have write access to this directory in the first place.
For your private needs, a shell function in your .profile or similar is sufficient.
something () {
PATH=newvalue command something "$#"
}
Related
Backup, first.
Before attempting to resolve this, people are worried about $PATH changes breaking things. Backup your path with echo $PATH > $HOME/bak/conf/path.bak and remember (don't put any raw files into the bak dir). Confirm your backup with cat $HOME/bak/conf/path.bak.
A minimal $PATH!
My desired path is /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin.
This is a minimalist path that should break nothing on most default Linux installs (Backup, first).
I have a huge $PATH that is completely useless (like 120 lines of $PATH--caused by WSL [more later]). So far, I only know how to add to path and remove single directories from $PATH via seg.
Sounds like it should pop right up on search engines. I have a headache in my eye--:
how do I set $PATH to a raw string or purge it so I can append from null?.
Bonus points for anyone who can tell me how to set $PATH from the contents of a file!?
Best guess:
import sys, sys.path.insert(0, '/your/path')--!NO This did not work!
Temporary workaround:
You can move the path to a neutral file (or use whatever means you know) and replace / with / and then use sed to cut the unwanted part out:
PATH=$(echo "$PATH" | sed -e 's/:\/tons:\/of:\/unwanted:\/garbage$//')
Make sure you put everything you don't want between s/ and $//').
Where is this long $PATH coming from?
Related question: How to remove the Win10's PATH from WSL --$PATH is inherited every time you re-launch or perform some other actions such as possibly changing users or environments.
Why is this question different:
As if there isn't enough typing, S.O. has a nuisance dialog that asks me to write and explain why purging $PATH is different from removing one directory/path from $PATH.
Because a punch in the face isn't a kick in the butt.
If I'm understanding your question, you simply want to know how to set your Bash PATH to a fixed string (or erase it completely) and have it persist across restarts of the shell?
If so, then I'll start off by saying that this isn't recommended as it will break other things. If not now, then later.
But if you really want to set the PATH to a fixed value every time Bash starts ...
Short answer first:
Assuming fairly "default" startup files (where ~/.profile sources ~/.bashrc):
Edit your ~/.profile (or ~/.bash_profile in the rare case it exists) and add:
# Remove entirely
unset PATH
export -n PATH
# Optionally
export PATH=/new/pathItem1:/new/path/Item2
Adding this to the bottom of the file should override any other PATH modifications done previously. However, future application installations can make modifications below that, therefore overriding it.
In a very rare case where you have an interactive Bash session that isn't startup by a login shell, you could also make the same modifications to ~/.bashrc.
(Not necessarily required if the modification is the last thing called during startup, but) make sure that there are no other PATH adjustments in your ~/.bashrc or ~/.bash_profile.
More explanation:
Where is this long $PATH coming from?
Your path in Bash in WSL can potentially come from a few sources:
WSL's init process sets a default PATH for the starting application (shell). This is currently (in WSL 0.70.8):
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib
Since this is done before your ~/.profile runs, there's no need to worry about disabling it.
As you've pointed out, WSL automatically appends the Windows path to the Linux path so that you can launch Windows executables. This can be disabled using the method in the question you linked, but it's not necessary if you simply want to override the entire PATH. Setting the PATH in your ~/.profile comes after WSL sets it for the initial process (your shell).
Bash itself has a built-in, default path that is hardcoded into it for "fallback" in case no other path is passed in. This rarely ever takes effect. Most Linux systems are going to set the PATH through some other mechanism, meaning the fallback never gets activated. A typical Linux system will set the default PATH via /etc/environment, but this isn't used under WSL since the init mechanism mentioned above is needed instead.
If using Systemd or another init system in WSL, then Bash (and other POSIX shells) will process /etc/profile and files in /etc/profile.d when starting. These files may contain PATH modifications. Again, since these come before your ~/.profile, (re)setting the PATH to a fixed value will override any of these settings.
Finally, your ~/.profile or ~/.bash_profile (for login shells) and ~/.bashrc (for interactive shells are read. Typically, PATH modifications in these files look something like:
export PATH="/new/path/item:$PATH"
This prepends to the path, making that new item take priority over the previous items.
However, leaving out the existing :$PATH part will simply set it to a string literal.
It should be obvious, but if you do this without including the "normal" OS paths, you will be unable to call any command that isn't built in to Bash already with specifying its fully qualified path. For instance, ls will fail. Note that you can still:
$ ls
ls: command not found
$ /usr/bin/ls
# works
Also note that ~/.profile is called for login shells. It is possible, however, to tell WSL to launch Bash without reading ~/.profile:
wsl ~ -e bash --noprofile
The resulting shell will still have the default WSL path in this case.
I started looking into files such as:
/etc/profile
~/.bash_profile
etc.
in order to locate where environment variables were defined. Unfortunately, I couldn't locate the $PATH variable. I am using Bash.
The initial PATH environment variable is inherited from ... whatever launched the shell. For example commands like sudo, sshd, whatever creates your shall after a desktop login.
There also appears to be a PATH that is hardwired into the bash binary for cases where an initial PATH is not inherited. (Look at the output from strings /bin/bash.)
Then various shell initialization scripts get a go at setting or updating PATH. For example, on Ubuntu the PATH variable is updated by /etc/profile.d/apps-bin-path.sh ... which is run by /etc/profile.
You should not worry (or even ask) where PATH is set, since you should not be trusting a random distro to put the right directories in the right sequence.
Instead, you set the PATH you need in your shell's profile. That's it.
As a starting point, POSIX mandates that getconf PATH returns the system's default PATH. If you have a $HOME/bin and there's a /usr/local/bin, then you add them.
Here's what this looks like on my machine:
PATH="$(/usr/bin/getconf PATH)"
PATH="$PATH:/usr/sbin"
PATH="$PATH:/usr/local/bin"
PATH="$PATH:$HOME/bin"
With this setup, it's easy to adapt the sequence. Maybe you don't like the ancient vim in /usr/bin/vi? Compile it yourself and move /usr/local/bin to the front.
I've got a makefile for installing my personal repo of config files, part of which is compiling my emacs scripts:
compile:
emacs -batch --eval "(progn (load \"~/.emacs\") (byte-recompile-directory \"~/.emacs.d\" 0))"
The problem is, on OSX, I have an alias called "emacs" that points to the Emacs.app binary for use in a terminal, this is defined in my ~/.bash_profile.
Now, no matter what I do, I can't seem to get the shell that Make is calling to read a startup file to load that alias, so that compilation step always fails.
Does anyone know how to do this?
.bash_profile is only read by interactive login shells. Exported environment variables set in it are inherited through the process environment, which means that these settings are generally available to all programs the user starts (if bash is indeed the login shell, of course).
No such inheritance happens for aliases, though. Bash supports exported functions, but that's an obscure feature which can easily break other programs (for example, those which assume that environment variable values do not contain newlines). If you go that route, you may have to use .bashrc instead, to make sure that these functions are exported by interactive bash shells which are not login shells.
I expected the easiest solution is to put a directory like $HOME/bin on the PATH (in .bash_profile or .bashrc, whatever works best) and put an emacs wrapper script into that directory which invokes the actual binary using exec /path/to/Emacs.app "$#" (or maybe just a symbolic link would do).
That is very strange. Aliases are not exported to sub-shells, and the .bash_profile script is only run by interactive shells: make doesn't invoke an interactive shell (by default). So, it's hard to understand how the shell make invokes would see that alias based on the information you've provided.
Maybe you set the BASH_ENV shell variable somewhere? You should never do that, unless you really know what you're doing.
Maybe you reset make's .SHELLFLAGS variable to force a login shell? You shouldn't to that either.
Anyway, you can try using command which avoids aliases etc. Unfortunately make doesn't know this is a shell-built in, so you have to convince it to run a shell. This will be fixed in the next release of GNU make but Apple will never ship that.
compile:
command emacs -batch --eval "(progn (load \"~/.emacs\") (byte-recompile-directory \"~/.emacs.d\" 0))" && true
I have made a shell script to run as terminal command, but the cd commands inside it is not effective and hence I want to run it with source so that the cd commands take effect.
script name : "project.sh"
I added this file to /usr/local/bin, made it executable by chmod +x project.sh and it runs fine, but the cd command is not working.
I know it runs in a child process and hence at the end terminal returns back to the starting directory, rendering no effect of cd commands inside project.sh.
The solutions presented at Sol:1 do not work for me, because they asks me to run source <file>, which is not possible if I want to use it as Bash command.
You use the source command:
source /usr/local/bin/project.sh
There's no way to make this happen automatically by typing the script name, that always runs the script in a subprocess. If you don't want to have to type this all out, you could create an alias in your .bashrc to simplify it:
alias project='source /usr/local/bin/project.sh'
Then typing project will be translated to that full command.
Of course, source <file> is a Bash command - it uses the source builtin to run script <file> in the context of the current shell rather than in a child process, thus allowing commands in <file> to change the current shell's environment, such as in terms of the working directory (using cd).
Using source, or its alias ., is (ultimately) the only way to achieve that.
If your intent is not to have to invoke <script> explicitly with source, you have two options, both of which are best defined in your Bash profile, ~/.bash_profile (since you're on OS X; on Linux, use ~/.bashrc[1]):
I'll assume that your script is /path/to/foo, and that you want to invoke it sourced as just foo:
Option 1: Define an alias: alias foo='source "/path/to/foo"'
Option 2: Define a function: foo() { source "/path/to/foo"; }
Both aliases and functions execute in the current shell, allowing you to effectively hide the source call behind a single command; aliases are generally a little easier to define, but functions offer more flexibility.
By virtue of the alias / function being defined in your Bash profile, which itself is implicitly sourced, the commands in /path/to/foo will affect your interactive shells' environment.
Note: Either definition of foo will only be available in interactive shells (those that (automatically) source ~/.bash_profile).
Additional steps would be needed to make foo work inside non-sourced scripts as well, but at that point you should ask yourself whether you're obscuring things by not making the fact that /path/to/foo is getting sourced explicit.
If you're writing a script that must be sourced for distribution to others:
Install the sourcing command in the user's shell profile / initialization file (as described above) on installation of your script.
If there is no installation process (and also to enable on-demand installation in general), implement a command-line option for your script such as i (--install) that performs this installation on demand.
Preferably, also implement an uninstallation option.
Either way, build logic into the script so that it refuses to run when run without sourcing, and have the error message contain instructions on how to install sourcing.
See this answer for how to detect sourcing.
A real-world implementation of the above - although more elaborate due to being multi-shell - is my typex utility; source code here.
[1] On OS X, Bash instances started by Terminal.app are login shells, which means that the only (user-specific) file that is automatically sourced on startup is ~/.bash_profile.
By contrast, on most Linux systems Bash instances are non-login shells, where only ~/.bashrc is automatically sourced.
While it is common practice to source ~/.bashrc from one's ~/.bash_profile, this has to be configured manually and therefore cannot be relied upon blindly.
What we're trying to do is call source bash_profile to reload a bash_profile file. The script grabs a person's bash_profile and load it onto a person's computer. The problem is that the source bash_profile won't persist outside the ruby script. After the script ends, the terminal looks the same as it did before. How can we make it so that source bash_profile persists outside the ruby script?
The bash_profile usually modifies the bash environment (installing functions, aliases, variables, readline bindings, etc.), and there is really no way to modify the environment of a parent bash process.
So the best you can do is end the ruby script by execing a new bash, specifying the -l (or --login) option to make it a login shell so that it will start by sourcing bash_profile. (You can also do this by making the first character of argument -, usually by setting it to -bash.)
If you have control over the way the ruby script is initiated, you might be able to cause it to be execed, in order that it replaces the parent bash process. That will make for a cleaner process tree.