Should PATH contain directories or full paths to binaries? - linux

I am trying to set up a correct PATH, but I'm wondering what it should contain. If I have
/usr/bin/ls
/usr/local/bin/ls
and I want to prefer the one in /usr/local/bin, which of the following should I use?
PATH=/usr/local/bin/ls:/usr/bin/ls
PATH=/usr/local/bin:/usr/bin
or something else entirely?
This is not per se a suitable question for Stack Overflow.
I expect this to be closed as General Computing or Too Broad;
but the answer is frequently needed by beginners, so I hope this won't be deleted.

PATH works only with directories, not with single files
From the POSIX standard (emphasis mine)
PATH
This variable shall represent the sequence of path prefixes that certain functions and utilities apply in searching for an executable file known only by a filename. The prefixes shall be separated by a colon ( ':' ). [...] The list shall be searched from beginning to end, applying the filename to each prefix, until an executable file with the specified name and appropriate execution permissions is found.
When you type in ls into your shell and your PATH is set to /usr/local/bin/ls:/usr/bin/ls then your shell will …
… look for an executable with the path /usr/local/bin/ls/ls (note the double ls at the end).
As that path does not exist on your system your shell will proceed to look for an executable with the path /usr/bin/ls/ls (double ls again). That path also doesn't exist.
The shell couldn't find an executable using all paths in PATH so your shell will print something like bash: ls: command not found.
So, what did we learn? Paths listed by PATH have to be directories. You cannot list single files. Therefore the correct answer in your case is PATH=/usr/local/bin:/usr/bin.
Where things get interesting
Imagine the following situation. You have two versions of program c1 and two versions of program c2. The different versions are stored in the directories /a/ and /b/.
/a/c1
/a/c2
/b/c1
/b/c2
How can we set PATH to prefer /a/c1 over /b/c1/ but at the same time /b/c2 over /a/c2?
Sadly, there is no way to achieve this directly as we can only specify directories in PATH. We have to move/rename some files or create symlinks and use the symlinks inside the paths. One possible solution:
mkdir /c
ln -s /a/c1 /c/c1
ln -s /b/c2 /c/c2
export PATH=/c:/a:/b
The trailing :/a:/b is not really necessary here. I included them under the assumption that /a and /b contain more executables than just c1 and c2.

Indeed, as you can easily find out through experimentation, the variable PATH should already contain a list of directories which are consulted in that order. In fact, you should already find that you have /usr/local/bin and /usr/bin in the default PATH, usually in this order (though perhaps with other directories between them, and certainly with more directories around them).
To inspect your current PATH value, try
echo "$PATH"
or for a slightly more human-readable rendering
echo "${PATH//:/$'\n'}" # bash only
or
echo "$PATH" | tr ':' '\012' # any POSIX system
If you managed to set your PATH to an invalid value (which would cause simple commands like ls and cat to no longer be found, and produce command not found errors) you can try
PATH=/usr/local/bin:/usr/bin:/usr
to hopefully at least restore the essential functionality so that you can use cp or a simple system editor to get back to the original, safe, system default PATH.

Related

Create executable file for comand line Linux

When ever I log into Linux I usually go straight to the same folder i was wondering if in stead of typing in:
$cd Document/..../..../..../..../....
I could create an executable so I could just type ./csFolder and it would go straight there.
You can add a shell function in your .bashrc and restart your terminal:
csf() {
cd Document/..../..../..../..../....
}
Whenever you want to go to that directory, you just run csf.
Yo can do a symlink
ln -s /path/to/file /path/to/symlink
In addition to the other options (though if you use the function/alias option you want to use an absolute path to the target directory so it works from wherever you happen to be) you can use the environment variable CDPATH to help with this if you have a location you often go to from various other locations.
From the POSIX specification:
CDPATH
A -separated list of pathnames that refer to directories. The cd utility shall use this list in its attempt to change the directory, as described in the DESCRIPTION. An empty string in place of a directory pathname represents the current directory. If CDPATH is not set, it shall be treated as if it were an empty string.
Which means that if you set CDPATH to the parent of your target directory you can just use cd dirname from anywhere and go directly to the directory you wanted to be in.

Tcl script cannot open file on Linux (Works on Windows)

I have a Tcl script that works on Windows. When I run it and pass in file names when prompted, usually with a relative directory path. On Windows the script will take absolute OR relative directory paths ... it works, no problems at all.
On Linux it will NOT work in the same way. It will only work if I spell out the full file path, e.g. /home/gxuser/input/test.txt ... It will NOT take relative directory paths like ./input/test.txt
By "work" I mean it should OPEN the file ... It is there and the permissions are fine.
On Linux the program fails with the following error:
couldn't open "./input/test.txt": no such file or directory
while executing
"open $upload r"
The offending line in the code is:
set infile [open $upload r]
What am I doing wrong? I presume, I have overlooked some nuance of the Tcl language, and assumed it should recognize relative paths.
Relevant information:
% puts $tcl_version
8.4
% info patchlevel
8.4.19
% uname -a
Linux gxengine 2.6.32-60-generic #122-Ubuntu SMP
It is possible that a function you call is changing the current working directory. There is no way to tell which one that could be without inspecting the code, but you can check if this is the case by displaying [pwd] before calling open.
Another way things could go wrong is by the script being started in different ways from Unix and Windows - e.g. by shortcut on Windows and from the command line off the PATH on Unix.
Where you have filenames passed in by the user and you can't be sure that code you call won't cd under your feet, you're advised to process the filenames into absolute filenames as soon as possible in your script, possibly even before any package require or source calls.
This is actually easy to do.
set absoluteFilename [file normalize $userProvidedFilename]
As long as this is done before the first cd (or chdir()/SetCurrentDirectory() at the C/C++ level) future changes of directory will not break the path. The script must be prepared to work with absolute filenames, but that's easily done.
You can turn a relative path into an absolute path with respect to any arbitrary directory with file join:
set filename [file join [file normalize $arbitraryLocation] $relativeFilename]
(The file join is smarter than just inserting a directory separator.)

Specify the command file location from two

I have two command files with same and both of them are located in PATH folder.
This means that which mycmd returns two different path like
$ which mycmd
/usr/local/bin/mycmd
/usr/bin/mycmd
How can I specify mycmd uses /usr/local/bin/mycmd(or the other)?
Any help will be appreciated!
If you feel reluctant to change your path, you can use an alias.
alias mycmd='/usr/local/bin/mycmd'
This is particularly useful if different commands would otherwise require differently ordered paths.
Add the statement to .bashrc, to prevent you from having to type it in at every shell session.
I think that file in directory that is earlier in you PATH will take precedence.
So if your path looks like this:
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/opt/bin:/usr/games/bin
then /usr/local/bin/mycmd will match before /usr/bin/mycmd and will be the one executed. If you want it the other way around, swap the order in your $PATH.

symbolic link without expanding $HOME or "~"?

the basic idea is that I want to link to path that's relative to $HOME, rather than explicitly expand the $HOME variable, as I want to make sure the link works on multiple machines, e.g.,
when I do
ln -s ~/data datalnk
I want it to be directed to directory /home/user/data on one machine which has a user $HOME of /home/user, and to /home/machine/user/data on another machine which has a user $HOME of /home/machine/user/data.
I cannot create a symbolic link on the second machine using
ln -s /home/machine/user /home/user
because I don't have the permission to do that, and I cannot link relative paths as the two machines have different hierarchies of directories.
anyideas on possible ways to fix or circumvent this?
EDIT:
what I am really trying to accompanish is to make the same link work on two macihnes, where the targets have the same directories in terms of their relative path to $/HOME only, not their absolute path, and not their relative path to the link either.
tl,dr it won't work
You can use an escaping mechanism such as single-quotes to get the ~ into the symbolic link:
> cd ~
> echo hello > a
> ln -s '~/a' b
However, ~ is a shell expansion and is not understood by the filesystem (actually, to the filesystem it's "just another character"). This is a good thing -- want the file-system layer to know about environment variables, as ~ is generally determined by $HOME?
> ls -l b
lrwxrwxrwx 1 root root 3 Oct 27 17:39 b -> ~/a
> ls b
ls: b: No such file or directory
You could still "manually" look at said symbolic link entries (as done with ls -l), but that would have to be done in a non-transparent fashion by a program (think of a ".LNK" in Windows). As can be seen, the filesystem just doesn't understand ~.
Happy sh'ing.
First of all: It can't be done directly. Symbolic links are plain text files, no extensions are performed. If you can't formulate a fixed relative or absolute path to the place you are referring, you can't symbolically link to it.
You can build a script to put links to appropriate directories in appropriate places, but the best way depends on your application.
The only way to make symlinks dynamic in this way is to use a relative path instead of an absolute path. In other words, don't start your path with /.
For example:
cd
ln -s data datalnk
At runtime your app or script will need to refer to ~/datalnk or $HOME/datalnk.
You haven't really said what you're trying to accomplish, so I can't really tell whether I'm solving your problem or suggesting that you need to go at it a different way.

When running a sh file in linux, why do I have to run ./name.sh?

I have a file called x.sh that I want to execute. If I run:
x.sh
then I get:
x.sh: command not found
If I run:
./x.sh
then it runs correctly. Why do I have to type in ./ first?
Because the current directory is not into the PATH environment variable by default, and executables without a path qualification are searched only inside the directory specified by PATH. You can change this behavior by adding . to the end of PATH, but it's not common practice, you'll just get used to this UNIXism.
The idea behind this is that, if executables were searched first inside the current directory, a malicious user could put inside his home directory an executable named e.g. ls or grep or some other commonly used command, tricking the administrator to use it, maybe with superuser powers. On the other hand, this problem is not much felt if you put . at the end of PATH, since in that case the system directories are searched first.
But: our malicious user could still create his dangerous scripts named as common typos of often used commands, e.g. sl for ls (protip: bind it to Steam Locomotive and you won't be tricked anyway :D).
So you see that it's still better to be safe that, if you type an executable name without a path qualification, you are sure you're running something from system directories (and thus supposedly safe).
Because the current directory is normally not included in the default PATH, for security reasons: by NOT looking in the current directory all kinds of nastiness that could be caused by planting a malicious program with the name of a legitimate utility can be avoided. As an example, imagine someone manages to plant a script called ls in your directory, and that script executes rm *.
If you wish to include the current directory in your path, and you're using bash as your default shell, you can add the path via your ~/.bashrc file.
export PATH=$PATH:.
Based on the explanation above, the risk posed by rogue programs is reduced by looking in . last, so all well known legitimate programs will be found before . is checked.
You could also modify the systemwide settings via /etc/profile but that's probably not a good idea.
Because current directory is not in PATH (unlike cmd in Windows). It is a security feature so that malicious scripts in your current directory are not accidentally run.
Though it is not advisable, to satisfy curiosity, you can add . to the PATH and then you will see that x.sh will work.
If you don't explicitly specify a directory then the shell searches through the directories listed in your $PATH for the named executable. If your $PATH does not include . then the current directory is not searched.
$ echo $PATH
/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin
This is on purpose. If the current directory were searched then the command you type could potentially change based on what directory you're in. This would allow a malicious user to place a binary named ls or cp into directories you frequent and trick you into running a different program.
$ cat /tmp/ls
rm -rf ~/*
$ cd /tmp
$ ls
*kaboom*
I strongly recommend you not add . to your $PATH. You will quickly get used to typing ./, it's no big deal.
You can't execute your file by typing simply
x.sh
because the present working directory isn't in your $PATH. To see your present working directory, type
$ pwd
To see your $PATH, type
$ echo $PATH
To add the current directory to your $PATH for this session, type
$ PATH=$PATH:.
To add it permanently, edit the file .profile in your home directory.

Resources