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

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

Related

Changing PWD in a script to allow for accessing file without prefixing full path

I know I should be able to change the current working directory of a bash script by doing something akin to
cd `dirname $MYPATH`
but for some reason this doesn't work (or not as I imagined it).
#!/bin/bash
WAYPATH="/home/user/articles"
TEST_PATH="/home/user/testing"
# Set working directory of the script to be testing
cd `dirname $TEST_PATH`
for i in $(ls $WAYPATH); do
another_command $i $i.r > $TEST_PATH/htmls/$i.html
done
My goal here is to allow the bash script to find the files located in TEST_PATH (which have matching name to those in WAY_PATH) without having to prefix them with the full path (because another_command) makes use of the whole argument passed to it.
So this is a lesson on understanding what commands do after reading about them on Stackexchange. I was using
cd `dirname $MYPATH`
following this answer where they achieved the desired result
cd `dirname $0`
$0 is the full path of the bash script, so dirname is required to return the path without the name of the file.
Instead, for an arbitrary supplied path is sufficient to do a simple
cd $MYPATH
as suggested in comments.

What does the colon do in PATH

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.

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

bash script doesn't find mkdir [duplicate]

This question already has answers here:
Getting "command not found" error in bash script
(6 answers)
Closed 2 years ago.
I've created a simple script to check if a folder exists and if not to create it. The script that follow
#!/bin/bash
PATH=~/Dropbox/Web_Development/
FOLDER=Test
if [ ! -d $PATH$FOLDER ]
then
echo $PATH$FOLDER 'not exists'
/bin/mkdir $PATH$FOLDER
echo $PATH$FOLDER 'has been created'
fi
works only if the mkdir command is preceded by /bin/. Failing in that, bash env output the error message "command cannot be found".
I though this could have been related to the system $PATH variable, but it looks regular (to me) and the output is as following:
/Library/Frameworks/Python.framework/Versions/2.7/bin:/bin:/usr/local/bin:/usr/bin:/sbin:/usr/local/sbin:/usr/sbin
I'm not sure whether the order with the different bin folders have been listed make any difference, but the /bin one (where the mkdir on my OSX Maverick) seems to reside is there hence I would expect bash to being able to execute this.
In fact, if I call the bash command from terminal, by typing just mkdir bash output the help string to suggest me how the mkdir command should be used. This suggests me that at a first instance bash is able to recognise the $PATH variable.
So what could be the cause? Is there any relation between the opening statement at the top of my .sh - #!/bin/bash - file and the "default" folder?
Thanks
Yeah, sometimes it is a bad idea to use capital letters for constant variables, because there are some default ones using the same convention. You can see some of the default variables here (Scroll to Special Parameters and Variables section). So it is better to use long names if you don't want to get any clashes.
Another thing to note is that you're trying to replicate mkdir -p functionality, which creates a folder if it does not exist (also it does create all of the parents, which is what you need in most cases)
One more thing - you always have to quote variables, otherwise they get expanded. This may lead to some serious problems. Imagine that
fileToRemove='*'
rm $fileToRemove
This code will remove all files in the current folder, not a file named * as you might expect.
One more thing, you should separate path from a folder with /. Like this "$MY_PATH/$MY_FOLDER". That should be done in case you forget to include / character in your path variable. It does not hurt to have two slashes, that means that /home/////////user/// folder is exactly the same /home/user/ folder.
Sometimes it is tricky to get ~ working, so using $HOME is a bit safer and more readable anyway.
So here is your modified script:
#!/bin/bash
MY_PATH="$HOME/Dropbox/Web_Development/"
MY_FOLDER='Test'
mkdir -p "$MY_PATH/$MY_FOLDER"
The problem is that your script sets PATH to a single directory, and that single directory does not contain a program called mkdir.
Do not use PATH as the name of a variable (use it to list the directories to be searched for commands).
Do learn the list of standard environment variable names and those specific to the shell you use (e.g. bash shell variables). Or use a simple heuristic: reserved names are in upper-case, so use lower-case names for variables local to a script. (Most environment variables are in upper-case — standard or not standard.)
And you can simply ensure that the directory exists by using:
mkdir -p ~/Dropbox/Web_Development
If it already exists, no harm is done. If it does not exist, it is created, and any other directories needed on the path to the directory (eg ~/Dropbox) is also created if that is missing.

Deleting a directory contents using shell scripts

I am a newbie to Shell scripting. I want to delete all the contents of a directory which is in HOME directory of the user and deleting some files which are matching with my conditions. After googled for some time, i have created the following script.
#!/bin/bash
#!/sbin/fuser
PATH="$HOME/di"
echo "$PATH";
if [ -d $PATH ]
then
rm -r $PATH/*
fuser -kavf $PATH/.n*
rm -rf $PATH/.store
echo 'File deleted successfully :)'
fi
If I run the script, i am getting error as follows,
/users/dinesh/di
dinesh: line 11: rm: command not found
dinesh: line 12: fuser: command not found
dinesh: line 13: rm: command not found
File deleted successfully :)
Can anybody help me with this?
Thanks in advance.
You are modifying PATH variable, which is used by the OS defines the path to find the utilities (so that you can invoke it without having to type the full path to the binary). The system cannot find rm and fuser in the folders currently specified by PATH (since you overwritten it with the directory to be deleted), so it prints the error.
tl;dr DO NOT use PATH as your own variable name.
PATH is a special variable that controls where the system looks for command executables (like rm, fuser, etc). When you set it to /users/dinesh/di, it then looks there for all subsequent commands, and (of course) can't find them. Solution: use a different variable name. Actually, I'd recommend using lowercase variables in shell scripts -- there are a number of uppercase reserved variable names, and if you try to use any of them you're going to have trouble. Sticking to lowercase is an easy way to avoid this.
BTW, in general it's best to enclose variables in double-quotes whenever you use them, to avoid trouble with some parsing the shell does after replacing them. For example, use [ -d "$path" ] instead of [ -d $path ]. $path/* is a bit more complicated, since the * won't work inside quotes. Solution: rm -r "$path"/*.
Random other notes: the #!/sbin/fuser line isn't doing anything. Only the first line of the script can act as a shebang. Also, don't bother putting ; at the end of lines in shell scripts.
#!/bin/bash
path="$HOME/di"
echo "$path"
if [ -d "$path" ]
then
rm -r "$path"/*
fuser -kavf "$path"/.n*
rm -rf "$path/.store"
echo 'File deleted successfully :)'
fi
This line:
PATH="$HOME/di"
removes all the standard directories from your PATH (so commands such as rm that are normally found in /bin or /usr/bin are 'missing'). You should write:
PATH="$HOME/di:$PATH"
This keeps what was already in $PATH, but puts $HOME/di ahead of that. It means that if you have a custom command in that directory, it will be invoked instead of the standard one in /usr/bin or wherever.
If your intention is to remove the directory $HOME/di, then you should not be using $PATH as your variable. You could use $path; variable names are case sensitive. Or you could use $dir or any of a myriad other names. You do need to be aware of the key environment variables and avoid clobbering or misusing them. Of the key environment variables, $PATH is one of the most key ($HOME is another; actually, after those two, most of the rest are relatively less important). Conventionally, upper case names are reserved for environment variables; use lower case names for local variables in a script.

Resources