Как удалить дублирующиеся директории из $PATH?

Вопрос или проблема

В некоторых из моих терминальных окон есть дубликаты в переменной PATH; что-то вроде этого:

PATH=/a/b:/c/d:/a/b:/c/d:/e/f:/a/b

Я предполагаю, что виноваты строки, подобные следующей, в некоторых моих скриптах:

PATH=/a/b:$PATH

После выполнения этого и того, и снова этого PATH становится очень длинным. Вот вопрос:

Существует ли команда bash для очистки PATH и подобных переменных окружения? Это должно быть скрипт на bash, поскольку нельзя выполнить утилиту и ожидать, что она изменит окружение вызывающей оболочки.

В приведенном выше примере очищенный PATH должен выглядеть так:

PATH=/a/b:/c/d:/e/f

Лучше не создавать дубликаты, чем пытаться удалить их позже. Это легко избежать с той техникой, которую я использую в своем .bashrc для добавления моего личного каталога bin/:

[ "${PATH#*$HOME/bin:}" == "$PATH" ] && export PATH="$HOME/bin:$PATH"

Я сделал это в то время, когда обновлял .bashrc, и хотел перезапустить его без перезапуска оболочки.

Если вы хотите добавить каталог в конец $PATH, вам нужно использовать двоеточие в начале:

[ "${PATH#*:$HOME/bin}" == "$PATH" ] && export PATH="$PATH:$HOME/bin"

Вы можете использовать расширение параметров, чтобы пройтись по PATH и удалить дубликаты, но это будет немного сложнее, и вам нужно будет решить, какую позицию следует оставить. Что-то вроде:

OLDPATH="$PATH"; NEWPATH=""; colon=""
while [ "${OLDPATH#*:}" != "$OLDPATH" ]
do  entry="${OLDPATH%%:*}"; search=":${OLDPATH#*:}:"
    [ "${search#*:$entry:}" == "$search" ] && NEWPATH="$NEWPATH$colon$entry" && colon=:
    OLDPATH="${OLDPATH#*:}"
done
NEWPATH="$NEWPATH:$OLDPATH"
export PATH="$NEWPATH"

После того как я написал это спонтанно и теперь протестировал, я должен был устранить большинство ошибок, и это должно быть адекватным руководством к тому, что вам нужно сделать. Оно оставляет последнее вхождение любых дубликатов, что соответствует тому, где они были бы, если бы вы использовали мой скрипт для предотвращения дубликатов с самого начала. В скрипте его, конечно, нужно вызывать с помощью команды ./source.

Следующее – это пример на bash.

#!/bin/bash
if [ -n "$PATH" ]; then
  old_PATH=$PATH:; PATH=
  while [ -n "$old_PATH" ]; do
    x=${old_PATH%%:*}       # первый оставшийся элемент
    case $PATH: in
      *:"$x":*) ;;          # уже есть
      *) PATH=$PATH:$x;;    # еще нет
    esac
    old_PATH=${old_PATH#*:}
  done
  PATH=${PATH#:}
  unset old_PATH x
fi
echo ${PATH}

Я написал этот простой скрипт на Python 3:

import os

# получаем $PATH
path = os.environ['PATH'].split(':')

# нормализуем все пути
path = map(os.path.normpath, path)

# удаляем дубликаты с помощью словаря
clean = dict.fromkeys(path)

# объединяем обратно в один путь
clean_path=":".join(clean.keys())

# вывод на stdout
print(f"PATH={clean_path}")

Я поместил скрипт в $HOME/scripts, затем добавил эту строку в конец моего .bashrc:

eval $(python3 $HOME/scripts/clean-path.py)

С версии Python 3.7 гарантируется, что словарь сохраняет порядок, поэтому clean_path будет содержать каталоги в том же порядке, что и оригинальный PATH.

С такой же неуверенностью я нашел этот вопрос полезным.
Так что я могу написать это в bash

TEST=$(echo -n $PATH | tr ":" "\n" | sort | uniq -c | tr -s " " | cut -d " " -f3 | tr "\n" ":") && echo ${TEST%:}

с выводом, вроде этого

/bin:/home/root/gowork/bin:/root/bin:/usr/local/go/bin:/usr/local/sbin:/usr/pgsql-10/bin:/usr/sbin

Вот версия с красивым выводом

if [[ -x /usr/bin/awk ]]; then
  export PATH="$(echo "$PATH" | /usr/bin/awk 'BEGIN { RS=":"; } { sub(sprintf("%c$", 10), ""); if (A[$0]) {} else { A[$0]=1; printf(((NR==1) ?"" : ":") $0) }}')"
  echo $PATH
else
  echo "AWK не находится по адресу /usr/bin/awk" # для истинно параноидальных
fi

а вот версия в одну строку

if [[ -x /usr/bin/awk ]]; then export PATH="$(echo "$PATH" | /usr/bin/awk 'BEGIN { RS=":"; } { sub(sprintf("%c$", 10), ""); if (A[$0]) {} else { A[$0]=1; printf(((NR==1) ?"" : ":") $0) }}')" ; fi

Просто попробуйте это:

echo -e ${PATH//:/'\n'} | sort | uniq | paste -sd: -

Microsoft technet решает эту проблему с помощью скрипта PowerShell. Этот скрипт идентифицирует и удаляет дубликаты из переменной PATH.

В нем говорится:

Иногда повторная установка программного обеспечения может добавить дублирующиеся записи в переменную окружения PATH. Поскольку у переменной окружения есть жестко закодированный предел в размере этой переменной, есть вероятность, что со временем вы можете достичь этого предела. Этот скрипт проверяет переменную окружения PATH и удаляет любые дублирующиеся записи пути.

Ответ или решение

Чтобы удалить дубликаты директорий из переменной окружения $PATH, можно использовать несколько подходов, как в bash, так и в Python. Эта операция важна для оптимизации рабочего окружения и предотвращения ошибок, связанных с конфликтующими версиями исполняемых файлов, расположенных в разных директориях.

Подход с использованием bash

В bash можно создать следующий скрипт, который будет проходить по элементам в $PATH, проверяя их на дубликаты:

#!/bin/bash
if [ -n "$PATH" ]; then
  old_PATH=$PATH:; PATH=
  while [ -n "$old_PATH" ]; do
    x=${old_PATH%%:*}       # первая оставшаяся запись
    case $PATH: in
      *:"$x":*) ;;         # уже есть
      *) PATH=$PATH:$x;;   # ещё нет
    esac
    old_PATH=${old_PATH#*:}
  done
  PATH=${PATH#:}  # удаление ведущего двоеточия
  export PATH
fi
echo ${PATH}

Этот скрипт:

  1. Проверяет, не пустая ли переменная $PATH.
  2. Создает временную переменную для обработки и добавляет двойное двоеточие, чтобы обеспечить правильную обработку разделителей.
  3. Извлекает первую запись и проверяет, была ли она уже добавлена в более новый $PATH. Если нет, то добавляет её.
  4. В конце удаляет ведущий символ : из переменной $PATH.

Подход с использованием Python

Если вы предпочитаете более современный и удобный способ, можно использовать Python. Вот пример простого скрипта на Python, который делает то же самое:

import os

# Получаем $PATH
path = os.environ['PATH'].split(':')

# Нормализуем все пути
path = map(os.path.normpath, path)

# Убираем дубликаты с помощью словаря
clean = dict.fromkeys(path)

# Объединяем обратно в единый путь
clean_path = ":".join(clean.keys())

# Выводим в stdout
print(f"PATH={clean_path}")

Этот скрипт:

  1. Разделяет переменную окружения $PATH на компоненты.
  2. Нормализует пути, чтобы обеспечить консистентность.
  3. Удаляет дубликаты, используя словарь, который сохраняет порядок (с Python 3.7).
  4. Собирает пути обратно в строку и выводит результат.

Достоинства каждого подхода

  • Bash-скрипт: подойдёт для тех, кто работает исключительно в Unix-подобных системах и отдает предпочтение простым, встроенным средствам оболочки.
  • Python-скрипт: предоставляет более гибкий и читаемый синтаксис, подходит для пользователей, знакомых с Python. Его можно легко расширить, добавив дополнительную логику обработки.

Рекомендации

  • Чтобы избежать создания дубликатов в будущем, вы можете добавлять новые директории в $PATH, проверяя, нет ли её уже в списке, как это описано в вашем примере с использованием .bashrc. Это поможет предотвратить проблему с дублированием на ранних стадиях.

Таким образом, хотя удаление дубликатов из $PATH может быть выполнено различными способами, использование скриптов, описанных выше, может значительно упростить задачу и гарантировать правильную работу вашей системы.

Оцените материал
Добавить комментарий

Капча загружается...