Does Bash have cache of executables?

Posted on

Problem :

I was just writing a C++ program on my Ubuntu 14.10 computer. As it is a client/server program, I has three terminal emulators open: one for writing code and compilation, one for client testing, and the last one for server testing. After a while, I thought I had a bug in my code. I lost over an hour tracking it down, and it turned out that getting out of directory and entering it again solved the whole problem. To be precise these were the commands I executed:

some_directory$ ./client
some_directory$ cd ..
$ cd some_directory
some_directory$ ./client

Without changing anything—and without recompilation—the two runs gave different results. The only thing I can think of is some kind of cache that would store old version of files, but I never heard about such a feature. Is there an explanation, and how to solve it (make it refresh automatically, without exitting directory)?

Solution :

I don’t think bash does anything that fancy (it does cache paths to executables specified without paths, but that doesn’t apply here, since you are specifying the path)

Sounds like the directory you were in was moved or mounted over. In the case of building software, the directory move is more likely.

Directory move case

An example of recreating the behaviour of a directory move, in terminal 1

cd /tmp
mkdir dir1
cd dir1
touch exampleFile

Then in terminal 2:

cd /tmp
mv dir1 dir2
mkdir dir1
cd dir1

Both shells show as being in a directory named ‘dir1’, but a listing will show different contents. If terminal2 creates a file called ‘exampleFile’, both shells will show ‘exampleFile’ in ‘dir1’ but they are different files. This is because the shell in terminal 1 is in fact in dir2 now, it just doesn’t know it. The shell in terminal 1 can get to the real ‘dir1’ via cd:

cd .

This looks weird but re-resolves the path.

Over mount case

This occurs when a shell (or any program) is in a directory and then a filesystem is mounted over top of it. For example, in terminal 1:

mkdir /tmp/dir1
cd /tmp/dir1

terminal 2:

mount /dev/whateverdev /tmp/dir1
cd /tmp/dir1

Terminal 1 sees the files from the original filesystem in that directory. Terminal 2 sees the files from /dev/whateverdev.

How to avoid

In the case of over mounting, this is mostly just being aware of what you are doing or how your machine is configured (for example, if an automounter is running when a usb drive is plugged in).

In the case of directory moves, this depends a bit more on the build system. If there is a rule that makes a backup of the old output directory by moving it and then creates a new directory for the new build, you’ll run into this situation quite frequently. Check your build rules for trickery.

Of course, this could also have been a one-time unintentional accident (similar to what was shown in the first example above). In which case, awareness that this can happen will help you out.

In General hash and sync

hash – built in command of bash

To speed up the operation when you execute a command the bash shell store its path in an hash table. Each shell instance has its own hash table. For more information read this excerpt from man bash:

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.
Bash uses a hash table to remember the full pathnames of executable files (see hash under SHELL BUILTIN COMMANDS below). A full search of the directories in PATH is performed only if the command is not found in the hash table.

You can substitute an entry in the list (e.g. the command client) with hash client; you can erase this entry from the list with hash -d client; you can reset the list completely with hash -r.

Sync– Synchronize data on disk with memory

The sync command ensures everything in memory is written to disk.

To speed up the operation often Linux can store some data in the kernel filesystem buffers, i.e., data which has been scheduled for writing via low-level I/O system calls[1].

On the other hands “Hard disks may default to using their own volatile write cache to buffer writes, which greatly improves performance while introducing a potential for lost writes”[2]

sync writes any data buffered in memory out to disk. This can
include (but is not limited to) modified superblocks, modified inodes,
and delayed reads and writes. This must be implemented by the kernel;
The sync program does nothing but exercise the `sync’ system call.
The kernel keeps data in memory to avoid doing (relatively slow)
disk reads and writes.
This improves performance, but …

[if you are interested continue to read here or with info coreutils sync].

Specific to your problem

If your problem depends on one of the aspects above you can try to write in only one line more than one command, and after quick recall them with the up arrow key, e.g.:

  • hash -d ./client; ./client,
  • sync ; ./client,
  • hash -d ./client ; sync ; ./client
  • (cd .. ; cd - ; ./client ), your own solution. Here you execute the client in a subshell that take in heritage all you have done in your shell (as variables…) but that will not return changes to the current shell.

Without a complete understanding of the operations involved in your compilation steps and in the dialling operation between client and server can be difficult to find the cause (it can even be a symbolic link, or an high latency on a NFS filesystem …). In this case you have to search the origin by trials and errors.