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:
$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:04emacs
oropenssl
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/usr/local/bin
comes first in thePATH
... – Stephen Kitt Oct 14 '17 at 07:38hash -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