Behavior of cd/bash on symbolic links - linux

Assume I have the folders ~/a/b in my home folder, and the folder b contains
a symbolic link to '..' named 'symlink'. Then I perform the following actions in
bash:
hm#mach:~$ cd a/b/symlink
hm#mach:~/a/b/symlink$ pwd -P
/home/hm/a
hm#mach:~/a/b/symlink$ cd ..
hm#mach:~/a/b$ pwd -P
/home/hm/a/b
pwd -P prints the current working directory, dereferencing all symbolic links.
Why is the working directory /home/hm/a/b at the end, and not /home/hm?

According to help cd,
Options:
-L force symbolic links to be followed: resolve symbolic
links in DIR after processing instances of `..'
-P use the physical directory structure without following
symbolic links: resolve symbolic links in DIR before
processing instances of `..'
In other words, -L means using the logical structure, whereas -P uses the actually physical directory structure.
The logical structure is like this,
$ tree a
a
└── b
└── symlink -> ..
The actual physical structure when you go to a/b/symlink is,
a
If you want to use the real .., then you must also use cd -P:
The -P option says to use the physical directory
structure instead of following symbolic links (see
also the -P option to the set builtin command);
the -L option forces symbolic links to be followed.
An example,
$ cd
$ cd a/b/symlink # physical location is at a/
$ cd .. # now is at a/b
$ cd symlink # goes back to a/b/symlink
$ cd -P .. # follow physical path (resolve all symlinks)
$ pwd -P # -P is optional here to show effect of cd ..
/home/sarnold
$

bash keeps track of the logical current directory path, as shown in your prompt, and interprets things like cd .. according to that. This makes things a little more consistent if you only use such paths in cd (or pushd), at the cost of unexpected things happening if you then expect the same thing to happen with paths in command arguments (or inside commands; emacs and vim have their own configurable rules for symlink handling, but most commands rely on the kernel to deal with it).

Related

How exactly does the cd ./ command work in Bash?

I'm just curious, when I'm using the cd command in bash, commands
cd foobar
and
cd ./foobar
work in the same way. I understand, that ./ refers to the current catalogue directory, but why then does "cd foobar" work? Is it just default, that when I'm not writing ./ on the beginning, program adds it on its own, or is it more complicated?
The cd command in bash (somewhat indirectly, as described below) invokes the chdir() syscall, behavior of which is specified by POSIX. The cd shell comand itself also has a POSIX specification for its behavior, in perhaps more detail than is appropriate for an accessible/readable definition.
Yes, the default for all operations (not only chdir() but also fopen() and others) is the current working directory.
This isn't specific to bash, but is operating-system-level behavior: "Current working directory" is part of the metadata about each process tracked by the kernel, and impacts filesystem-level requests made to the kernel: Any program, in any language, can call chdir("foo") or open("foo", O_RDONLY), and behavior will be to look for foo in the current directory, as inherited from the parent process or modified with prior chdir() calls.
That said, for the purposes of bash, cd ./foo is more specific than merely cd foo: The former says that you explicitly want the foo subdirectory of the current working directory. In bash, if the CDPATH shell variable is set, then cd foo will look in all directories listed in it, whereas cd ./foo explicitly only asks for foo under the current working directory.
Try this experiment:
# setup
tempdir=$(mkdir -d "${TMPDIR:-/tmp}/cdpath-test.XXXXXX")
mkdir -p "$tmpdir/i-am-a-test-directory"
CDPATH=".:$tempdir"
# this works, because CDPATH is honored
cd /
cd i-am-a-test-directory
# this does not, because you're explicitly asking for a directory that does not exist
cd /
cd ./i-am-a-test-directory
# cleanup
rm -rf "$tempdir"
unset CDPATH
Ignoring the CDPATH environment variable for the time being, the name after cd is a directory path name. There are only two sorts of path name in Unix:
absolute names that begin with a /
relative names which don't start with a /
All relative names are treated by the kernel as relative to the current directory — as if the name was prefixed by ./.
If you have CDPATH set, it complicates the search, and it is then conceivable that cd somewhere and cd ./somewhere land you in different directories. A directory name with no leading / and with no leading . or .. component (where cd .hidden doesn't count — the leading component is .hidden— but cd ./visible or cd ../visible do count) is searched for using CDPATH.
For example, consider this tree structure (only directories shown):
. - current directory
./somewhere
./src
./src/somewhere
Suppose you have CDPATH=src:. Then cd somewhere will go to ./src/somewhere and cd ./somewhere will go (unsurprisingly) to ./somewhere. If you type a name such as cd src/somewhere, the cd command will search for a sub-directory /xyz/pqr/src/somewhere for each directory /xyz/pqr on CDPATH in turn. I perpetually use CDPATH (7 directories on my own machines; 14 on my work machines).
Rule of thumb: use CDPATH=:…whatever…
That puts the current directory first on the search path, and is usually the most sane behaviour.
A similar story applies to searching for ordinary external (non-aliased, non-function, non-builtin) commands. Command names that start with a slash are absolute pathnames; command names containing a slash are relative to the current directory; command names without a slash are searched for on $PATH.

How do I make symbolic link but cd should show physical directory name

I have a folder in my home directory called ~/bestphotosever
So I make a symlink as follows:
cd ~
ln -s bestphotosever bpe
cd bpe
pwd
#-- here is what pwd shows
--> /home/myuser/bpe
#What I would like it to show is the physical name
--> /home/myuser/bestphotosever
Any ideas how to do this using 'ln' command.
You can use "pwd -P" instead, avoids all symlinks and prints the "real" path
edit: I just realized i might not have answered your question.
Are you sure "you want" a hardlink?
Some info on hard link vs symlink.
https://askubuntu.com/questions/210741/why-are-hard-links-not-allowed-for-directories
If you just want to get into the folder with a shorter name, either tabcompletion or aliases are your friends.

Why is my symbolic link creating a file and not a folder?

I want a create a symbolic link to a folder. The follow command will create a file with the link name but I'm trying to link to the source folder. What am I doing wrong?
ln -s /Users/me/somefolder somefolder
This creates a file "somefolder" in my current directory. How do I create a symbolic link to the folder and it's contents?
Thanks!
You need to use absolute path names to create the links.
For example, I'm now at
$ pwd
/home/alex/my_folder
And I'm creating a symbolic link to the folder "directoryA" in a sub-directory under my pwd (present working directory):
$ ln -s $PWD/directoryA $PWD/temp/link_to_directoryA
In this case variable $PWD holds absolute path to my working directory.
You can surely use your absolute path without any variables like this:
$ ln -s /home/alex/my_folder/directoryA /home/alex/my_folder/temp/link_to_directoryA
You need to be inside the same directory where you create the symbolic link
For instance:
cd /Users/me
ln -s somefolder somefolderNewName
Not creating a directory is an expected behavior.
When you do
ls -ali
It should show something beginning with;
lrwxrwxrwx
In which "l" represents symlink and allows you to traverse using cd.
NOTICE: ln command will not complain when you provide an invalid source path. And this will result with an error message when you try cd in to that.
Late for the party..
This is what worked for me..
if you want to create a symbolic link from sourceFolder to destinationFolder
you should be inside the parent of the destinationFolder "parentOfDestinationFolder" while doing so.
I think you have what you want, you just don't know it. A link has an entry in the directory, just like data files or directories do. You can see this most clearly if you run ls -l in the directory where you're creating the link.
You can use your link as if it were a directory, e.g.:
$ cd somefolder
You might also like to know that if you change directory this way, the parent of somefolder will be the directory that contains the link. If you don't want that, use:
$ cd -P somefolder

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.

A confusion in APUE2(about symbolic link in UNIX)

The original text is below.It is in Section 4.22
The program in Figure 4.24 changes to a specific directory and then calls getcwd to print the working directory. If we run the program, we get
$ ./a.out
cwd = /var/spool/uucppublic
$ ls -l /usr/spool
lrwxrwxrwx 1 root 12 Jan 31 07:57 /usr/spool -> ../var/spool
Note that chdir follows the symbolic link as we expect it to, from Figure 4.17 .but when it goes up the directory tree, getcwd has no idea when it hits the /var/spool directory that it is pointed to by the symbolic link /usr/spool. This is a characteristic of symbolic links.
What does the author really mean by saying that the program hits the /var/spool?
What is the characteristic of symbolic links pointed out by the author?
I did not really understand.
Note that some shells, notably bash, keep track of whether you arrived at a given directory by chasing a symbolic link, and print the current directory accordingly. At least bash has options to cd to do a physical or logical change directory:
cd [-L|-P] [dir]
Change the current directory to dir. The variable HOME is the default dir. [...] The -P option says to use the physical directory structure instead of following symbolic links (see also the -P option to the set builtin command); the -L option forces symbolic links to be followed. An argument of - is equivalent to $OLDPWD. If a non-empty directory name from CDPATH is used, or if - is the first argument, and the directory change is successful, the absolute pathname of the new working directory is written to the standard output. The return value is true if the directory was successfully changed; false otherwise.
In the scenario shown, where /usr/spool is a symbolic link to /var/spool, then:
$ pwd
/
$ cd /usr/spool/uucppublic
/usr/spool/uucppublic
$ cd -L ..
/usr/spool
$ cd /usr/spool/uucppublic
/usr/spool/uucppublic
$ cd -P ..
/var/spool
$
For most people, a plain cd .. would do the same as cd -L ... You can choose to have bash do the same as cd -P .. instead if you prefer (using set -P or set -L).
The process of finding the pathname of the current directory should be understood too. Logically, the process (kernel) opens the current directory (.) and reads the inode number (and device number). It then opens the parent directory (..), and reads entries from that until it finds one with the matching inode number (and device number). This then gives it the last component of the pathname. It can now repeat the process, finding the the inode number of the next directory up, and opening its parent directory (../..), etc, until it reaches the root directory (where the inode number for both . and .. is the same, and the value is conventionally 2). Note that this even works across mount points. Beware of auto-mounted remote (NFS) file systems, though; it can be really slow if you scan through a directory containing several hundred automounted machines - as the naïve search outline above mounts all the machines until it finds the correct one. So, actual getcwd() functions are cleverer than this, but it explains how the path of the current directory is found. And it also shows why the process will not encounter /usr/spool when evaluating the directory under /var/spool/uucppublic - it simply never opens the /usr directory.
Note that the realpath() function (system call) takes a name possibly referencing symlinks and resolves it to a name that contains no symlinks at all. Passed /usr/spool/uucppublic, it would return /var/spool/uucppublic, for example.
Expanding on what #undor_gongor wrote:
Each process has a current working directory. It's not stored as the path name of the directory; it's a reference to the directory itself.
If it were stored as a path name, then the getcwd() function's job would be trivial: just print the path name. Instead, it has to readi the current directory, open its .. entry, then open that directory's .. entry, and so forth until it reaches the root (i.e., a directory whose .. entry points to the directory itself). It builds up the full path of the current directory in reverse order as it does this.
Since .. can't be a symlink, this process is not affected by symbolic links.
(Shells might have a $PWD or $CWD variable, or a pwd built-in, that is affected by symlinks; these typically work by remembering the string that was passed to cd or pushd.)
Assume you have a symlink /usr/spool pointing to /var/spool.
It says if you follow that symlink (e.g. cd /usr/spool), you end up in the pointed-to directory (/var/spool). Then, the information that you followed a symlink is lost. You are in /var/spool as if you had done cd /var/spool directly.
A further cd .. brings you to /var (as opposed to /usr).
UPDATE:
As pointed out by Keith Thompson and Jonathan Leffler, there are some shells that do remember the path you followed (i.e. /usr/spool). In such shells, cd ..
would go to /usr/. However, programs started from such a shell would still see /var/spool as the working directory.
This is probably the reason the author let you write a program for displaying cwd (to work-around such shells' internals).

Resources