4

This is related to What is the purpose of the hash command? After installing a program I am looking for a way to tell all open shells to update their cache. I often have 6 to 8 of them open and I don't want to have to run hash -r 8 or 10 times. Anything more than once is a waste of time and effort.

After installing a program in one shell, is it possible to either (1) have all shells rebuild their view of cached programs; or (2) rebuild the table and use that table for all other shells? If it is possible, then how do we do it?


Here is the relevant section of man 1 hash, but it does not discuss how to achieve results globally.

   hash [-lr] [-p filename] [-dt] [name]
          Each time hash is invoked, the full pathname of the command name
          is  determined  by searching the directories in $PATH and remem‐
          bered.  Any previously-remembered pathname is discarded.  If the
          -p option is supplied, no path search is performed, and filename
          is used as the full filename of  the  command.   The  -r  option
          causes  the  shell  to  forget all remembered locations.  The -d
          option causes the shell to forget  the  remembered  location  of
          each  name.   If the -t option is supplied, the full pathname to
          which each name corresponds is printed.  If multiple name  argu‐
          ments  are  supplied  with  -t,  the  name is printed before the
          hashed full pathname.  The -l option causes output  to  be  dis‐
          played in a format that may be reused as input.  If no arguments
          are given, or if only -l is supplied, information  about  remem‐
          bered  commands  is printed.  The return status is true unless a
          name is not found or an invalid option is supplied.
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • If you installed a program in one of the $PATH directories, you shouldn't even need to rehash. Curious why you're using -r, as well. – Jeff Schaller Oct 13 '17 at 23:59
  • @Jeff - You certainly need it even when installing on $PATH; see, for example, Path not being honored? on OS X stack exchange. As for -r, I staterted using it to try and get Bash to forget the old version of the program installed in /usr/bin (re: OS X question). If you have another recommendation, then I'd be happy to use it. –  Oct 14 '17 at 00:04
  • You're replacing an existing $PATH'd binary with a different one of the same name? – Jeff Schaller Oct 14 '17 at 00:05
  • @jeff - Yes. For example, I might be upgrading (displacing?) emacs or openssl located in /usr/bin with a new one located in /usr/local/bin. I never overwrite or delete what's in /usr/bin. I install something new in /usr/local/bin. –  Oct 14 '17 at 00:07
  • Won't the relative sequence in $PATH influence which one runs? Or are you also deleting the /usr/bin copy? – Jeff Schaller Oct 14 '17 at 00:09
  • @Jeff presumably /usr/local/bin comes first in the PATH... – Stephen Kitt Oct 14 '17 at 07:38
  • @jww, could you spell out the steps to reproduce this issue, in this question, please? – Jeff Schaller Oct 14 '17 at 21:29
  • 1
    @JeffSchaller I added a pseudo-script at the bottom of my solution which I think produces something like what jww is experiencing. – igal Oct 16 '17 at 13:47
  • It does sound like you'll need a hash -d that-program in each of the shells; seems a rather niche problem to have: several long-running shells open, with stale hash table entries for a program that you've replaced that occurs twice in the shell's $PATH. – Jeff Schaller Oct 16 '17 at 13:59
  • @JeffSchaller I completely agree. I wouldn't consider this to be a problem or even much of an inconvenience and I'm skeptical that there is a solution of the form requested which doesn't introduce more potential complexity than it's worth. That said, we should try to answer questions as asked, right? – igal Oct 18 '17 at 16:51

1 Answers1

1

Here is the TLDR solution: if you want to programmatically execute commands in pre-existing shell sessions then you can use the ttyecho program.

WARNING: Note that this is not in general a safe operation and is sensitive to the state of each session. As user StephenKitt points out, this approach requires that each terminal session have a prompt that is waiting to receive input.

An alternative solution would be to effectively disable path-hashing by adding the PROMPT_COMMAND='hash -r' statement to your ~/.bashrc file. This won't affect currently running sessions, but will prevent this issue from arising in the future.

Finally, you could consider using a terminal multiplexer such as tmux, which supports executing commands in multiple terminals simultaneously.

A more detailed response follows.


It seems to me that the basic functionality that you're looking for is the ability to programmatically execute commands in other active shell sessions. I found that this question (of how to execute commands in other shell sessions) has been addressed elsewhere on this site, e.g.:

It seems that the easiest way to execute commands in other active shell sessions is to use the ttyecho utility. The ttyecho program is a small, self-contained, single-file C program. Here are a couple of references to the ttyecho source-code and documentation:

For completeness, here is the source code:

// ttyecho.c
// Original author: Pratik Sinha
// http://www.humbug.in/2010/utility-to-send-commands-or-data-to-other-terminals-ttypts/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>

void print_help(char *prog_name) {
    printf("Usage: %s [-n] DEVNAME COMMAND\n", prog_name);
    printf("Usage: '-n' is an optional argument if you want to push a new line at the end of the text\n");
    printf("Usage: Will require 'sudo' to run if the executable is not setuid root\n");
    exit(1);
}

int main (int argc, char *argv[]) {
    char *cmd, *nl = "\n";
    int i, fd;
    int devno, commandno, newline;
    int mem_len;
    devno = 1; commandno = 2; newline = 0;
    if (argc < 3) {
        print_help(argv[0]);
    }
    if (argc > 3 && argv[1][0] == '-' && argv[1][1] == 'n') {
        devno = 2; commandno = 3; newline=1;
    } else if (argc > 3 && argv[1][0] == '-' && argv[1][1] != 'n') {
        printf("Invalid Option\n");
        print_help(argv[0]);
    }
    fd = open(argv[devno],O_RDWR);
    if(fd == -1) {
        perror("open DEVICE");
        exit(1);
    }
    mem_len = 0;
    for ( i = commandno; i < argc; i++ ) {
        mem_len += strlen(argv[i]) + 2;
        if ( i > commandno ) {
            cmd = (char *)realloc((void *)cmd, mem_len);
        } else { //i == commandno
            cmd = (char *)malloc(mem_len);
        }

        strcat(cmd, argv[i]);
        // strcat(cmd, " ");
    }
  if (newline == 0)
        usleep(225000);
    for (i = 0; cmd[i]; i++)
        ioctl (fd, TIOCSTI, cmd+i);
    if (newline == 1)
        ioctl (fd, TIOCSTI, nl);
    close(fd);
    free((void *)cmd);
    exit (0);
}

If you download the source code or copy-paste it to a file named ttyecho.c, then you should be able to compile the program like so:

gcc ttyecho.c -o ttyecho

Once the program is compiled you should be able to use it to run any command you'd like in anther shell session. This included being able to programmatically run the hash -r command in every active shell session. Here is one way to clear the hashed commands on all of the shell sessions on Mac OS X:

for _tty in /dev/ttys*; do ./ttyecho -n 'hash -r' ${_tty}; done

On a Linux machine you would do something like this instead:

for _tty in /dev/pts/*; do ./ttyecho -n 'hash -r' ${_tty}; done

Since some comments asked for a way to reproduce the issue, I've included the following script to illustrate how hashing can affect which program is executed.

# Create two bin directories
mkdir -p ~/local/bin1
mkdir -p ~/local/bin2

# Add the directories to the PATH, with bin2 preceding bin1
export PATH="${HOME}/local/bin2:${HOME}/local/bin1:${PATH}"

# Create a test script and put it in the bin1 directory
cat <<HEREDOC > ~/local/bin1/test.sh
#!/bin/bash
# test.sh

echo "This is test #1, it's in ~/local/bin1"
HEREDOC

# Make the script executable
chmod +x ~/local/bin1/test.sh

# Verify that the script is found by using the "which" command
which test.sh

# Output:
#
#   /home/username/local/bin1/test.sh

# Verify that the script is found by using the "type" command
type test.sh

# Output:
#
#   test.sh is /home/username/local/bin1/test.sh

# Test the script
test.sh

# Output:
#
#   This is test #1, it's in ~/local/bin1

# Now hash the test script
hash test.sh

# Verify that the script has been hashed by using the "type" command
type test.sh

# Output:
#
#   test.sh is hashed (/home/username/local/bin1/test.sh)

# Now create a second test script and put it in bin2 to shadow the first
cat <<HEREDOC > ~/local/bin2/test.sh
#!/bin/bash
# test.sh

echo "This is test #2, it's in ~/local/bin2"
HEREDOC

# Make the second test script executable
chmod +x ~/local/bin2/test.sh

# Verify that the bin2 test script take priority over the bin1 test script
which -a test.sh

# Output:
#
#   /home/username/local/bin2/test.sh
#   /home/username/local/bin1/test.sh

which test.sh

# Output:
#
#   /home/username/local/bin2/test.sh

# Try to run the test script
test.sh

# Ouput:
#
#   This is test #1, it's in ~/local/bin1

# NOTE: Even though bin2/test.sh comes before bin1/test.sh in the PATH,
#       it's bin1/test.sh that is executed. What's going on?

# Use the "type" command to see which script is being executed
type test.sh

# Output
#
#   test.sh is hashed (/home/username/local/bin1/test.sh)

# NOTE: That explains the seeming contradiction.

# Clear the hashed command
hash -d test.sh

# Check that the hashed command has been cleared
type test.sh

# Output:
#
#   test.sh is /home/username/local/bin2/test.sh

# Run the test.sh command
test.sh

# Output:
#
#   This is test #2, it's in ~/local/bin2

I also found an alternative approach described elsewhere on this site:

The solution there was to use the PROMPT_COMMAND variable to continuously clear the path cache, e.g. by adding the following line to your ~/.bashrc:

PROMPT_COMMAND='hash -r'

Note that this will only affect new sessions and not your pre-existing sessions unless you also execute that command in those sessions.

Finally, you may want to consider using a terminal multiplexer such as tmux. Here is a relevant post from this site:

igal
  • 9,886
  • 1
    How do you know ttyecho is writing to a prompt that’s waiting for a command? Say you have an IRC client in one terminal, an editor in another, and a shell with a complex command you’re working on (that’s supposed to delete a bunch of files, for added fun)... – Stephen Kitt Oct 18 '17 at 15:16
  • @StephenKitt Is this a rhetorical question? If so, I should say that I don't think that this is a good idea. But it sounds like what the question was asking for, no? Personally I don't consider having to run hash -d a few times to be much of an inconvenience. – igal Oct 18 '17 at 15:31
  • @StephenKitt I added an alternative solution (or approximate solution) that doesn't involve injecting commands into active sessions. – igal Oct 18 '17 at 15:42
  • 1
    No, it’s not a rhetorical question. It’s an interesting approach, but it does have significant drawbacks, especially for someone like the OP with a bunch of long-lived shells lying around. You end up sending text strings to all terminals, not just “programmatically execut[ing] commands in other active shell sessions”. I would recommend adding the fact that you don’t think this is a good idea to the answer itself... – Stephen Kitt Oct 18 '17 at 15:43
  • @StephenKitt I completely agree. I've added a cautionary note, per your suggestion. – igal Oct 18 '17 at 15:56