What does the colon do in PATH - bash

I am new to bash, and I saw people often add : after a directory when modifying PATH. After searching for a while, I did not find an answer for that, or I believe I did not search it correctly. So I hope I could get an answer here.
Example:
/Users/chengluli/anaconda/bin:/Users/chengluli/.rbenv/shims:/
What does the : after bin and shims do?

: is the separator. The PATH variable is itself a list of folders that are "walked" through when you run a command.
In this case, the folders on your PATH are:
/Users/chengluli/anaconda/bin
/Users/chengluli/.rbenv/shims
/

As others have said, the : is a separator (Windows uses a semi-colon ;). But you are probably thinking of a trailing colon : at the end of the PATH variable. For example:
/Users/chengluli/anaconda/bin:/Users/chengluli/.rbenv/shims:
From the bash man pages:
A zero-length (null) directory name in the value of PATH indicates the current directory. A null directory name may appear as two adjacent colons, or as an initial or trailing colon.
Putting the current directory in the PATH is generally considered a security risk and a bad idea. It is particularly dangerous when using the root user.
By the way, bash only uses $PATH on the first call of an external program, after that it uses a hash table. See man bash and the hash command

If you run ls -l 123 at the command line, you are telling bash to find the command called ls in the filesystem. However, ls is just a file name, bash needs the absolute path of ls in the filesystem. So bash searches for a file called ls in a list of default directories, one by one in order.
A list of default directories is stored in the PATH variable, separated by :.

Th quotation from output of man bash command
PATH
The search path for commands. It is a colon-separated list of directories in which the shell looks for commands (see COMMAND EXECUTION below). A zero-length (null) directory
name in the value of PATH indicates the current directory. A null directory name may appear as two adjacent colons, or as an initial or trailing colon. The default path is sys‐tem-dependent, and is set by the administrator who installs bash. A common value is ``/usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin''.
If you have questions about bash script or environment variable, please use man bash firstly.

Related

SOLVED Error in BASH tab-completion with variable

I encounter an error with BASHs tab-completion with a variable being part of a filename or path.
The variable points to a certain path and that variable gets generated during boot time. Due to an scripting error or maybe intentional it has a double "//" in it but usually such double / is of no concern.
Lets assume the variable is named $LIVESOURCE and points to /mnt/sda1/DIRECTORY1//directory2 - it has no ending "/", but it always refers to a folder, not a file.
In this path there are other files and folders I want to access, either read files or copy files into folder(s).
So I do the following: I type e.g.
$ ls $LIVESOURCE/rootcopy/home/username/.moonchild\ productions/pale\ moon/
and while the cursor is at the end of the typed line I press [TAB] or [TAB][TAB] to see which files and folders are there to be found in the target folder.
But for some reason BASH changes the above after I press [TAB] once into
$ ls \$LIVESOURCE/rootcopy/home/username/.moonchild\ productions/pale\ moon/
Which of course results in an error. Why is BASH doing that?
When I do type the following
$ cp /usr/local/bin/dummyfile $LIVESOURCE/rootcopy/usr/local/bin/
and press [TAB] BASH is not changing the $LIVESOURCE part into \$LIVESOURCE - but here $LIVESOURCE is not at the beginning of the CLI input. Instead BASH lists the files and folders that are to be found in the target directory - as it should.
My BASH is V5.0.11(1) x86-64 Slackware.
(I did search for "bash tab-completion variable" but found no appropriate topic that would answer my question)

Execute program with relative path and 'PATH' environment in Bash shell

Bash Environment
Given a very simple disk structure as below
And environment path variable set to dir1 and dir2 as below
$ env|grep PATH
returns :-
PATH=/:/usr/bin:/e/path/to/directory/dir1:/e/path/to/directory/dir2
execution of the program fails as below
$ bin/prog.exe
bash: bin/prog1.exe: No such file or directory
or also
$ /bin/prog1.exe
bash: /bin/prog.exe: No such file or directory
however if we modify path to include /bin
PATH=/:/usr/bin:/e/path/to/directory/dir1/bin:/e/path/to/directory/dir2/bin
it does of course work
$ prog1.exe
Hello from prog1 ...
My question is how do I make paths relative to 'environment' PATH work in bash?
In practise I am given some files that have 10's of relative paths generated to many different virtual root locations, to which I cant change.
It is also not possible to use a full path, or just the executable name of (which we know works) for this scenario.
See man bash for explanation (emphasis mine):
If the name is neither a shell function nor a builtin, and contains no slashes, bash searches each element of the PATH for a directory containing an executable file by that name.
There's no such thing as relative path lookup. If your command name contains any / characters, it is treated as a path relative to your current working directory only. If it has no / characters, then the shell will look only in the exact directories, not any subdirectories under them, listed in your PATH.
Relative path lookups would raise a host of issues related to order in which subdirectories should be searched.
As #choroba implies, you can't do what you're asking to do.
If you need to find a program in a subdirectory of one of the entries in your PATH, you'll have to iterate until you find it:
rel_path="bin/prog.exe"
IFS=: read -ra paths <<<"$PATH"
for path in "${paths[#]}"; do
if [[ -x "$path/$rel_path" ]]; then
exe="$path/$rel_path"
break
fi
done
if [[ -z "$exe" ]]; then
echo "cannot find $rel_path"
else
echo "found $rel_path as $exe"
fi

Blacklist program from bash completion

Fedora comes with "gstack" and a bunch of "gst-" programs which keep appearing in my bash completions when I'm trying to quickly type my git aliases. They're of course installed under /usr/bin along with a thousand other programs, so I can't just remove their directory from my PATH. Is there any way in Linux to blacklist these specific programs from appearing for completion?
I've tried the FIGNORE and GLOBIGNORE environment variables but they don't work, it looks like they're only for file completion after you've entered a command.
In 2016 Bash introduced an option for that. I'm reproducing the text from this newer answer by zuazo:
This is rather new, but in Bash 4.4 you can set the EXECIGNORE variable:
aa. New variable: EXECIGNORE; a colon-separate list of patterns that
will cause matching filenames to be ignored when searching for commands.
From the official documentation:
EXECIGNORE
A colon-separated list of shell patterns (see Pattern Matching) defining the list of filenames to be ignored by command search using
PATH. Files whose full pathnames match one of these patterns are not
considered executable files for the purposes of completion and command
execution via PATH lookup. This does not affect the behavior of the [,
test, and [[ commands. Full pathnames in the command hash table are
not subject to EXECIGNORE. Use this variable to ignore shared library
files that have the executable bit set, but are not executable files.
The pattern matching honors the setting of the extglob shell option.
For Example:
$ EXECIGNORE=$(which pytest)
Or using Pattern Matching:
$ EXECIGNORE=*/pytest
I don't know if you can blacklist specific files, but it is possible to complete from your command history instead of the path. To do that add the following line to ~/.inputrc:
TAB dynamic-complete-history
FIGNORE is for SUFFIXES only. It presumes for whatever reason that you want to blacklist an entire class of files. So you need to knock off the first letter.
E.g. To eliminate gstack from autocompletion:
FIGNORE=stack
Will rid gstack but also rid anything else ending in stack.

Strange PATH behavior in win-bash

I installed win-bash on Windows 7 and I'm getting the following strange behavior.
bash$ cat C:/Home/.bashrc
PATH="C:/Program\ Files/GnuWin32/bin:C:/Windows/system32"
bash$ . C:/Home/.bashrc
bash$ echo $PATH
C:/Program\ Files/GnuWin32/bin:C:/Windows/system32
bash$ which diff
which: no diff in (.;C;\Program\ Files\GnuWin32\bin;C:\Windows\system32)
bash$ which ls
which: no ls in (.;C;\Program\ Files\GnuWin32\bin;C:\Windows\system32)
Why are the PATH values different?
The PATH value returned by which contains .:C;\Program\ Files\GnuWin32\bin
Note:
the ".:" in the beginning that does not exist in the bash PATH value.
the "C;" (not C:) contains a semi-colon instead of a colon.
the which PATH value has back slashes (\\) instead of forward slashes (/)
Where is which sourcing these PATH values?
I can not find any other .bashrc or .profile or profile files anywhere on the machine.
In addition,
bash$ diff file-abc.txt file-xyz.txt
1c1
< abc
---
\> xyz
bash$ ls file-abc.txt
file-abc.txt
Both diff and ls work on the command line even though which can not find the diff or ls commands.
Both diff and ls are located in C:/Program\ Files/GnuWin32/bin
But which returns C;\Program\ Files\GnuWin32\bin (note C; not C:) which is why which can not find ls or diff.
Again, where is which sourcing these PATH values?
In my bash script named Try1.sh I have these lines.
\`diff $CURRENT_FILE $NEW_FILE\`
\`ls $CURRENT_FILE\`
The diff command fails with
Try1.sh: 21c21: command not found
The ls command succeeds. Why?
Both diff and ls live in the same PATH location C:/Program\ Files/GnuWin32/bin.
Windows has a different search algorithm to UNIX-like systems. On Windows the first directory to be searched is the directory which the parent program (.exe) was loaded from, then the current directory, then C:/Windows/system32 is searched. That's where the directory names are coming from.
The path environment variable is only used as a last resort!
For a full discussion on this, see MSDN entry for CreateProcess
which is also showing the Windows path directory separator as ;, rather than : which UNIX-like systems use. Also, / or \ are valid as a directory separator in a Windows path, but only / is valid on UNIX.
Also note that environment variables (like path) are not case sensitive on Windows, but on UNIX they are.
EDIT: I have been trying to track down the source code for win-bash but can't find it. I found some source code for which in GNUUtils, but can't be sure that it is the same version as you are using. The version I looked at, 2.4, makes assumptions about Windows which are not necessarily correct.
After downloading the binary for win-bash, I found that the bundled which is indeed version 2.4, and looks the same as the source code I have been looking at.
It is a separate program and not integrated with the rest of the shell code. To answer the question on directory separators and path separators, they are hard-coded for Windows (sys.h):
#define DIRSEP '\\'
#define PATHSEP ';'
The path is read from the environment variable using getenv.
Further edit:
The command
\`diff $CURRENT_FILE $NEW_FILE\`
is invalid. It is capturing the output from diff and then trying to execute it. 21c21 is the output from diff, and of course there is no such program as 21c21. Just use:
diff $CURRENT_FILE $NEW_FILE

Replacement for source in sh

I need to set the environment variables, usually we do this by
source script.sh
But now, I am automating it during the boot process and it looks like the root boots by default with sh shell. How do I source this script in sh?
The dot command '.' is the equivalent of the C Shell (and Bash) source command. It is specified by POSIX (see dot), and supported by the Bourne and Korn shells (and zsh, I believe).
. somefile
Note that the shell looks for the file using $PATH, but the file only has to be readable, not executable.
As noted in the comments below, you can of course specify a relative or absolute pathname for the file — any name containing a slash will not be searched for using $PATH. So:
. /some/where/somefile
. some/where/somefile
. ./somefile
could all be used to find somefile if it existed in the three different specified locations (if you could replace . with ls -l and see a file listed).
Pedants of the world unite! Yes, if the current directory is the root directory, then /some/where/somefile and ./some/where/somefile would refer to the same file — with the same real path — even without links, symbolic or hard, playing a role (and so would ../../some/where/somefile).
tl;dr With sh (as opposed to bash) the argument must contain a slash: source ./script.sh, not just source script.sh. Unless script.sh can be found in PATH.
Dot (.) command exists in both bash and sh. Additionally, bash aliases it as source. From bash manual:
https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html#index-source
source
source filename
A synonym for . (see Bourne Shell Builtins).
https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html#index-_002e
. (a period)
. filename [arguments]
Read and execute commands from the filename argument in the current shell context. If filename does not contain a slash, the PATH variable is used to find filename. When Bash is not in POSIX mode, the current directory is searched if filename is not found in $PATH.
From POSIX:
If file does not contain a /, the shell shall use the search path specified by PATH to find the directory containing file. Unlike normal command search, however, the file searched for by the dot utility need not be executable.

Resources