1. Overview
When we want to execute a program in Linux, the shell searches for the program in a list of directories. This list is stored in the $PATH environment variable. In this tutorial, we’ll see how to remove paths from the $PATH variable. Additionally, we’ll see how to find and remove duplicated entries in the $PATH variable.
2. Introduction to the Problem
When we start a new Linux session, the system fills the $PATH variable with a list of directories separated by a colon. We can see what is stored in the $PATH variable using the printenv command:
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/lib64/qt5/bin
As we see, there are several directories in that $PATH variable. We can also notice that Java added its own directories to the $PATH variable. Additionally, we can see that the directory /usr/lib64/qt5/bin is duplicated. There may be a situation when we don’t want to have certain directories in the $PATH variable. For instance, we may want to remove Java’s directories. In the following sections, we’ll see we can remove the undesired directory. Alternatively, we’ll also write a bash function called remove_from_path that receives a directory as a parameter and removes it from that $PATH variable. Also, we may want to make the $PATH variable shorter and easier to read by removing duplicated entries. Therefore, we’ll write another bash function called remove_duplicates_from_path that removes duplicated entries leaving only one instance of each directory.
3. How to Remove a Path
There are different alternatives to remove a path from the $PATH variable. We can use one or another depending on if we want a permanent or temporary solution.
3.1. Removing It Permanently
If we want to remove the path permanently, we can modify the script that is adding the directory to the $PATH variable. Usually, the script /etc/profile sets the $PATH variable with some base directories like /usr/bin. Also, the scripts inside the /etc/profile.d directory sometimes also add paths to the $PATH variable. Finally, each Linux user can have the scripts ~/.profile, ~/.bash_profile, and ~/.bashrc, which may also add directories to the $PATH variable. If we find the path in any of those scripts and modify it, we can remove the path permanently. For instance, we can find the following lines in the /etc/profile.d/jdk.sh script:
export JAVA_HOME=/usr/lib/java
export PATH="${PATH}:${JAVA_HOME}/bin:${JAVA_HOME}/jre/bin"
Notice that the last line adds Java’s directories to the $PATH variable. We can comment or remove the last line from the /etc/profile.d/jdk.sh script to remove Java’s path from the $PATH variable permanently. After removing the path from the file, when a user starts a new session, it won’t have that directory in the $PATH variable.
3.2. Manually Removing It Temporarily
If we want to remove the path temporarily, we can overwrite the $PATH variable with a new value. However, the temporary solution works only for the current user session. We can print the content of the $PATH variable, analyze which paths we want to keep and which to remove, and then set $PATH with a new path list. Let’s change the $PATH variable to remove the path to the java command:
$ which java
/usr/lib/java/bin/java
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/lib64/qt5/bin
$ PATH=/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib64/qt5/bin
$ which java
which: no java in (/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib64/qt5/bin)
In this case, we first tested if Bash could find the java command using the which command. Then, we removed Java’s directories from the $PATH variable. Finally, we used the which command again to verify that the command is no longer there.
3.3. Writing a Function
The previous method involves some manual work that is not possible if we want to remove a path within a bash script. Therefore, we can write a function to programmatically remove a directory from the $PATH variable. This way, we can easily use the function inside a script. Also, we can receive the directory we want to remove as a parameter. If we follow the same idea from the manual method, we can iterate over the $PATH variable with a for loop, checking each path. The $IFS special variable can help us iterate over the directories list. If we set $IFS to the colon character, then the for loop will use that character to split the string. We should restore $IFS to its original value if we change it. Let’s write the remove_from_path function to remove a path received by parameter:
$ remove_from_path()
{
DIR=$1
NEWPATH=
OLD_IFS=$IFS
IFS=:
for p in $PATH; do
if [ $p != $DIR ]; then
NEWPATH=${NEWPATH:+$NEWPATH:}$p
fi
done
IFS=$OLD_IFS
PATH=$NEWPATH
}
As we can see, the function is simple. We store the directory we want to remove in the $DIR variable and iterate over the $PATH list after setting the colon character as a separator in $IFS. We start with an empty variable $NEWPATH, and in each loop iteration, we decide if we want to append the path to $NEWPATH. Finally, we restore the $IFS variable to its original value and replace the $PATH variable with $NEWPATH. We use the ${NEWPATH:+$NEWPATH:} notation. This is a parameter expansion. This notation has two parts, NEWPATH:+, and $NEWPATH:. The shell writes $NEWPATH, and the colon only if $NEWPATH is not empty. If $NEWPATH is empty, the shell leaves an empty string. We use this notation to avoid adding the colon if there isn’t any path yet. Let’s test it to remove Java’s directories:
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/lib64/qt5/bin
$ remove_from_path /usr/lib/java/bin
$ remove_from_path /usr/lib/java/jre/bin
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib64/qt5/bin
As we see, we remove both Java’s directories using our remove_from_path function.
3.4. Using a One-Liner
Finally, there is a one-liner alternative to our remove_from_path function. We can use a combination of tr, grep, and paste to split, remove, and join the paths. Let’s take a look:
$ PATH=$(tr : '\n' <<<"$PATH" | grep -x -v "/usr/lib/java/bin" | paste -sd:)
We can see there are three steps in this function, and we store the result in the $PATH variable. First, we use tr to replace the colon character with a newline from $PATH. Then, we use grep to remove the undesired directory. We can notice we use the -x and -v parameters. We use -x to tell grep to match only when the whole line matches. And we use -v to invert the result, so grep removes the lines that match. Finally, we join grep‘s result with paste, and we set the delimiter to the colon character. This way, we recreate the $PATH variable without the path we want to remove. If we want, we can also wrap the one-liner in a function, and receive the directory as a parameter. Let’s rewrite our remove_from_path function:
$ remove_from_path() {
DIR=$1
PATH=$(tr : '\n' <<<"$PATH" | grep -x -v "$DIR" | paste -sd:)
}
Let’s use it to remove Java’s directories:
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/lib64/qt5/bin
$ remove_from_path /usr/lib/java/bin
$ remove_from_path /usr/lib/java/jre/bin
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib64/qt5/bin
4. How to Find and Remove Duplicated Paths
In our $PATH example, the /usr/lib64/qt5/bin directory is duplicated. This is not an error, but having the same path more than once is redundant. So, we may want to clean the $PATH variable and remove duplicated directories. We can write a function to programmatically find and remove duplicated directories. To find the duplicated entries in a function, we can loop the paths list and copy each path only if we haven’t added it yet. To know if we have already added a path, we can use associative arrays and store each path we loop over as an index in that array. So, we know we hadn’t added a path yet if it is not already an index. Let’s write the remove_duplicates_from_path function:
$ remove_duplicates_from_path() {
OLD_IFS=$IFS
IFS=:
NEWPATH=
unset EXISTS
declare -A EXISTS
for p in $PATH; do
if [ -z ${EXISTS[$p]} ]; then
NEWPATH=${NEWPATH:+$NEWPATH:}$p
EXISTS[$p]=yes
fi
done
IFS=$OLD_IFS
PATH=$NEWPATH
}
This function is similar to our first remove_from_path function. We use $IFS to split $PATH using the colon character, and we use $NEWPATH to build the new path. Finally, we restore $IFS to its original value and set $PATH to the new paths list. However, we also added the $EXISTS array. First, we unset it to clean any previous value. Then, we declare it as an associative array using declare -A EXISTS. This allows us to use strings as an index. To see if the path is already present, we use [ -z ${EXISTS[$p]} ]. This is true if $EXISTS doesn’t contain any value at the $p index. As the $p variable is the current directory in the iteration, we are testing if we have already seen the path before or not. If this is the first time we see the path, we add it to $NEWPATH using colons to separate the values. Then, we add $p as an index to the $EXISTS array, so we know we have already added that path. Let’s test the function:
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib/java/bin:/usr/lib/java/jre/bin:/usr/lib64/qt5/bin
$ remove_duplicates_from_path
$ printenv PATH
/usr/local/bin:/usr/bin:/bin:/usr/games:/usr/lib64/qt5/bin:/usr/lib/java/bin:/usr/lib/java/jre/bin
The /usr/lib64/qt5/bin path is not duplicated anymore after we called remove_duplicates_from_path.
5. Conclusion
In this article, we saw how to remove redundant paths from the $PATH variable. First, we looked at removing specific paths we don’t want to be in $PATH. We also saw we can replace the $PATH variable manually. Alternatively, we learned how to write a function to remove a path received as an argument. Finally, we also saw how to remove duplicated paths by writing a function to programmatically find and remove the duplicates.