Добавить каталог в $ PATH, если его там еще нет

126

Кто-нибудь написал функцию bash для добавления каталога в $ PATH, только если его там еще нет?

Я обычно добавляю в PATH что-то вроде:

export PATH=/usr/local/mysql/bin:$PATH

Если я создаю свой PATH в .bash_profile, он не будет прочитан, если только сеанс, в котором я нахожусь, не является сеансом входа в систему - что не всегда верно. Если я создаю свой PATH в .bashrc, то он запускается с каждой подоболочкой. Поэтому, если я запускаю окно терминала, затем запускаю screen и затем запускаю сценарий оболочки, я получаю:

$ echo $PATH
/usr/local/mysql/bin:/usr/local/mysql/bin:/usr/local/mysql/bin:....

Я собираюсь попробовать создать функцию bash, add_to_path()которая добавляет каталог, только если его там нет. Но если кто-то уже написал (или нашел) такую ​​вещь, я не буду тратить на это время.

Даг Харрис
источник
См. Stackoverflow.com/questions/273909/… для получения информации о некоторой инфраструктуре, которая может помочь.
dmckee
unix.stackexchange.com/questions/4965/…
Чиро Сантилли 新疆 改造 中心 法轮功 六四 事件
Если вы сформулируете проблему как «только добавление, если ее там еще нет», вы будете грубо удивлены, когда придет день, когда важно, чтобы вставленный элемент находился в начале, но он там не заканчивается. Лучшим подходом было бы вставить элемент, а затем удалить дубликаты, поэтому, если новая запись уже была там, она будет эффективно перемещена в начало.
Дон Хэтч

Ответы:

125

Из моего .bashrc:

pathadd() {
    if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
        PATH="${PATH:+"$PATH:"}$1"
    fi
}

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

Кроме того, это добавляет новый каталог в конец пути; чтобы поставить в начале, используйте PATH="$1${PATH:+":$PATH"}"вместо PATH=строки выше .

Гордон Дэвиссон
источник
26
Мне не все равно.
Деннис Уильямсон
4
@Neil: Это работает, потому что сравнивается, ":$PATH:"а не просто"$PATH"
Гордон Дэвиссон
3
@GordonDavisson: я прошу прощения, мой тест был неверным, и вы правы.
Нил
2
@GordonDavisson Какой смысл в фигурных скобках. Кажется, я не могу ${PATH:+"$PATH:"}
разгадать
5
@ Mark0978: Вот что я сделал, чтобы исправить проблему, указанную Букзором. ${variable:+value}означает проверить, variableопределено ли и имеет ли оно непустое значение, и дает ли оно результат оценки value. По сути, если PATH является непустым для начала, он устанавливает его в значение "$PATH:$1"; если он пуст, он устанавливает его просто "$1"(обратите внимание на отсутствие двоеточия).
Гордон Дэвиссон
19

В продолжение ответа Гордона Дэвиссона, это поддерживает несколько аргументов

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

Таким образом, вы можете сделать pathappend path1 path2 path3 ...

За предлог,

pathprepend() {
  for ((i=$#; i>0; i--)); 
  do
    ARG=${!i}
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="$ARG${PATH:+":$PATH"}"
    fi
  done
}

Подобно pathappend, вы можете сделать

pathprepend path1 path2 path3 ...

Гийом Перро-Аршамбо
источник
3
Это здорово! Я сделал одно небольшое изменение. Для функции 'pathprepend' удобно читать аргументы в обратном порядке, так что вы можете, например, сказать, pathprepend P1 P2 P3и в итоге получить PATH=P1:P2:P3. Чтобы получить такое поведение, измените for ARG in "$@" doнаfor ((i=$#; i>0; i--)); do ARG=${!i}
ishmael
Спасибо @ishmael, хорошее предложение, я отредактировал ответ. Я понимаю, что вашему комментарию уже более двух лет, но с тех пор я не вернулся. Я должен выяснить, как получить стековую электронную почту, чтобы попасть в мой почтовый ящик!
Гийом Перро-Аршамбо
14

Вот кое-что из моего ответа на этот вопрос в сочетании со структурой функции Дуга Харриса. Он использует регулярные выражения Bash:

add_to_path ()
{
    if [[ "$PATH" =~ (^|:)"${1}"(:|$) ]]
    then
        return 0
    fi
    export PATH=${1}:$PATH
}
Деннис Уильямсон
источник
Это работало для меня, только используя $1вместо${1}
Андрей
@ Андрей: Да, в этом случае фигурные скобки не нужны. Я не уверен, почему я включил их.
Деннис Уильямсон
10

Поместите это в комментарии к выбранному ответу, но комментарии, кажется, не поддерживают форматирование PRE, поэтому добавьте ответ здесь:

@ gordon-davisson Я не большой поклонник ненужных цитат и конкатенации. Предполагая, что вы используете версию bash> = 3, вы можете вместо этого использовать встроенные в bash регулярные выражения и сделать:

pathadd() {
    if [ -d "$1" ] && [[ ! $PATH =~ (^|:)$1(:|$) ]]; then
        PATH+=:$1
    fi
}

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

Кристофер Смит
источник
1
Комментарии поддерживают formatting using the backtickтолько, но вы не получите никакого достойного контроля над абзацами.
кодек
Это ставит сложение в конец. Часто желательно добавить в начало, чтобы переопределить существующие местоположения.
Деннис Уильямсон
@DennisWilliamson Это справедливо, хотя я бы не рекомендовал это как поведение по умолчанию. Нетрудно понять, как сменить предоплату.
Кристофер Смит
@ChristopherSmith - re: unnecessary quotingподразумевает, что вы заранее знаете, что $PATHэто не ноль. "$PATH"делает это нормально, независимо от того, является ли PATH нулевым или нет. Аналогично, если $1содержит символы, которые могут запутать анализатор команд. Помещение регулярного выражения в кавычки "(^|:)$1(:|$)"предотвращает это.
Джесси Чисхолм
@JesseChisholm: На самом деле, я считаю, что точка зрения Кристофера состоит в том, что правила отличаются между [[и ]]. Я предпочитаю , чтобы цитировать все , что может понадобиться , чтобы быть в кавычках, если это не вызывает его на провал, но я считаю , что он прав, и что котировки на самом деле не нужны вокруг $PATH. С другой стороны, мне кажется, что вы правы $1.
Скотт
7
idempotent_path_prepend ()
{
    PATH=${PATH//":$1"/} #delete any instances in the middle or at the end
    PATH=${PATH//"$1:"/} #delete any instances at the beginning
    export PATH="$1:$PATH" #prepend to beginning
}

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

Рассел
источник
Спасибо, это хорошее элегантное решение, но я обнаружил, что мне нужно сделать PATH=${PATH/"... а не PATH=${PATH//"... чтобы заставить его работать.
Марк Бут
Форма с двойным слешем должна соответствовать любому количеству совпадений; одиночная косая черта совпадает только с первой (ищите «Замена шаблона» на странице руководства bash). Не уверен, почему это не сработало ...
andybuckley
Это не удается в необычном случае, который $1является единственным входом (без двоеточий). Запись становится в два раза.
Деннис Уильямсон
Он также удаляет слишком агрессивно, как указал PeterS6g .
Деннис Уильямсон
6

Вот альтернативное решение, которое имеет дополнительное преимущество удаления избыточных объектов:

function pathadd {
    PATH=:$PATH
    PATH=$1${PATH//:$1/}
}

Единственный аргумент этой функции добавляется к PATH, и первый экземпляр той же строки удаляется из существующего пути. Другими словами, если каталог уже существует в пути, он продвигается вперед, а не добавляется как дубликат.

Функция работает, добавляя двоеточие к пути, чтобы гарантировать, что все записи имеют двоеточие спереди, а затем добавляя новую запись к существующему пути, удаляя эту запись. Последняя часть выполняется с использованием ${var//pattern/sub}обозначения Bash ; см. руководство по bash для более подробной информации.

Роб Хейг
источник
Хорошая мысль; некорректная реализация. Подумайте, что произойдет, если вы уже /home/robertв себе PATHи в себе pathadd /home/rob.
Скотт
5

Вот мой (я полагаю, он был написан Оскаром, системным администратором моей старой лаборатории много лет назад), он был в моем bashrc целую вечность. Он имеет дополнительное преимущество, позволяя вам добавлять или добавлять новый каталог по желанию:

pathmunge () {
        if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
           if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
           else
              PATH=$1:$PATH
           fi
        fi
}

Использование:

$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /bin/
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin
$ pathmunge /sbin/ after
$ echo $PATH
/bin/:/usr/local/bin/:/usr/bin:/sbin/
terdon
источник
5

Для предваряющих мне нравится решение @ Russell, но есть небольшая ошибка: если вы попытаетесь добавить что-то вроде "/ bin" к пути "/ sbin: / usr / bin: / var / usr / bin: / usr / local / bin: / usr / sbin "он заменяет" / bin: "3 раза (когда он вообще не совпадал). Объединяя исправление для этого с добавлением решения от @ gordon-davisson, я получаю это:

path_prepend() {
    if [ -d "$1" ]; then
        PATH=${PATH//":$1:"/:} #delete all instances in the middle
        PATH=${PATH/%":$1"/} #delete any instance at the end
        PATH=${PATH/#"$1:"/} #delete any instance at the beginning
        PATH="$1${PATH:+":$PATH"}" #prepend $1 or if $PATH is empty set to $1
    fi
}
PeterS6g
источник
4

Простой псевдоним как этот ниже должен сделать трюк:

alias checkInPath="echo $PATH | tr ':' '\n' | grep -x -c "

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

Пример использования:

$ checkInPath "/usr/local"
1
$ checkInPath "/usr/local/sbin"
1
$ checkInPath "/usr/local/sbin2"
0
$ checkInPath "/usr/local/" > /dev/null && echo "Yes" || echo "No"
No
$ checkInPath "/usr/local/bin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin" > /dev/null && echo "Yes" || echo "No"
Yes
$ checkInPath "/usr/local/sbin2" > /dev/null && echo "Yes" || echo "No"
No

Замените команду echo на addToPath или похожий псевдоним / функцию.

Нагул
источник
Использование «grep -x», вероятно, быстрее, чем цикл, который я вставил в свою функцию bash.
Даг Харрис
2

Вот что я взбил:

add_to_path ()
{
    path_list=`echo $PATH | tr ':' ' '`
    new_dir=$1
    for d in $path_list
    do
        if [ $d == $new_dir ]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}

Сейчас в .bashrc у меня есть:

add_to_path /usr/local/mysql/bin

Обновленная версия после комментария о том, что мой оригинал не будет обрабатывать каталоги с пробелами (спасибо за этот вопрос за указание использовать IFS):

add_to_path ()
{
    new_dir=$1
    local IFS=:
    for d in $PATH
    do
        if [[ "$d" == "$new_dir" ]]
        then
            return 0
        fi
    done
    export PATH=$new_dir:$PATH
}
Даг Харрис
источник
1
Это может произойти сбой , если любое имя каталога уже PATHсодержит пробелы, *, ?или [... ].
Скотт
Хороший вопрос ... но я парень из старой школы Linux и никогда не буду использовать путь с пробелами в нем :-)
Даг Харрис
Хорошо, что вы не создаете вещи с пробелами в именах. Позор вам за написание кода, который не может справиться с ними, когда они существуют. И при чем тут «парень из старой школы»? Windoze, возможно, популяризировал эту идею (спасибо, Documents and Settingsи Program Files), но Unix поддерживает имена путей, содержащие пробелы, еще до появления Windoze.
Скотт
2

Я немного удивлен, что никто еще не упомянул об этом, но вы можете использовать, readlink -fчтобы преобразовать относительные пути в абсолютные и добавить их в PATH как таковые.

Например, чтобы улучшить ответ Гийома Перро-Аршамбо,

pathappend() {
  for ARG in "$@"
  do
    if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]; then
        PATH="${PATH:+"$PATH:"}$ARG"
    fi
  done
}

становится

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ] && [[ ":$PATH:" != *":$ARG:"* ]]
        then
            if ARGA=$(readlink -f "$ARG")               #notice me
            then
                if [ -d "$ARGA" ] && [[ ":$PATH:" != *":$ARGA:"* ]]
                then
                    PATH="${PATH:+"$PATH:"}$ARGA"
                fi
            else
                PATH="${PATH:+"$PATH:"}$ARG"
            fi
        fi
    done
}

1. Основы - что хорошего это делает?

Команда readlink -f(среди прочего) преобразует относительный путь в абсолютный путь. Это позволяет вам сделать что-то вроде

$ cd / path / to / my / bin / dir
$ pathappend .
$ echo "$ PATH"
<your_old_path> : / path / to / my / bin / dir

2. Почему мы дважды тестируемся на наличие PATH?

Хорошо, рассмотрим приведенный выше пример. Если пользователь говорит из каталога во второй раз, будет . Конечно, не будет присутствовать в . Но тогда будет установлено значение (эквивалент абсолютного пути ), который уже находится в . Поэтому нам нужно избегать добавления во второй раз.pathappend ./path/to/my/bin/dirARG..PATHARGA/path/to/my/bin/dir.PATH/path/to/my/bin/dirPATH

Возможно, что еще более важно, основная цель readlink, как следует из названия, состоит в том, чтобы посмотреть на символическую ссылку и прочитать путь, который она содержит (то есть указывает на). Например:

$ ls -ld /usr/lib/perl/5.14
-rwxrwxrwx  1   root   root    Sep  3  2015 /usr/lib/perl/5.14 -> 5.14.2
$ readlink /usr/lib/perl/5.14
5.14.2
$ readlink -f /usr/lib/perl/5.14
/usr/lib/perl/5.14.2

Теперь, если вы говорите pathappend /usr/lib/perl/5.14, и у вас уже есть /usr/lib/perl/5.14в вашей PATH, ну, это нормально; мы можем просто оставить все как есть. Но, если его /usr/lib/perl/5.14еще нет в PATH, мы вызываем readlinkи получаем ARGA= /usr/lib/perl/5.14.2, а затем добавляем это к PATH. Но подождите минуту - если вы уже сказали pathappend /usr/lib/perl/5.14, значит, у вас уже есть /usr/lib/perl/5.14.2PATH, и, опять же, мы должны проверить это, чтобы не добавлять его во PATHвторой раз.

3. В чем дело if ARGA=$(readlink -f "$ARG")?

В случае, если неясно, эта строка проверяет, readlinkуспешно ли . Это просто хорошая, защитная практика программирования. Если мы собираемся использовать вывод команды  m как часть команды  n (где m  <  n ), целесообразно проверить, не завершилась ли команда  m, и обработать это каким-либо образом. Я не думаю, что, скорее всего, readlinkэто потерпит неудачу, но, как обсуждалось в разделе Как получить абсолютный путь к произвольному файлу из OS X и других областей, readlinkявляется изобретением GNU. Он не указан в POSIX, поэтому его доступность в Mac OS, Solaris и других не-Linux Unix-системах вызывает сомнения. (На самом деле, я только что прочитал комментарий, который говорит:readlink -fпохоже, не работает на Mac OS X 10.11.6, но realpathработает «из коробки» ». Итак, если вы работаете в системе, которая не имеет readlinkили где readlink -fне работает, вы можете изменить это использование скрипта realpath.) Установив сеть безопасности, мы сделаем наш код несколько более переносимым.

Конечно, если вы находитесь в системе, которая не имеет readlink(или  realpath), вы не захотите делать .pathappend .

Второй -dтест ( [ -d "$ARGA" ]) действительно, вероятно, не нужен. Я не могу придумать ни одного сценария, в котором $ARGкаталог является readlinkуспешным, но  $ARGAне является каталогом. Я просто скопировал и вставил первое ifутверждение, чтобы создать третье, и оставил там  -dтест из-за лени.

4. Любые другие комментарии?

Да уж. Как и многие другие ответы здесь, этот тест проверяет, является ли каждый аргумент каталогом, обрабатывает его, если он есть, и игнорирует его, если это не так. Это может (или не может) быть адекватным, если вы используете pathappend только в « .» файлах (например, .bash_profileи .bashrc) и других скриптах. Но, как показал этот ответ (выше), вполне возможно использовать его в интерактивном режиме. Вы будете очень озадачены, если вы делаете

$ pathappend /usr/local/nysql/bin
$ mysql
-bash: mysql: command not found

Вы заметили, что я сказал nysql в pathappendкоманде, а не mysql? И это pathappendничего не говорит; он просто молча проигнорировал неверный аргумент?

Как я уже говорил выше, это хорошая практика для обработки ошибок. Вот пример:

pathappend() {
    for ARG in "$@"
    do
        if [ -d "$ARG" ]
        then
            if [[ ":$PATH:" != *":$ARG:"* ]]
            then
                if ARGA=$(readlink -f "$ARG")           #notice me
                then
                    if [[ ":$PATH:" != *":$ARGA:"* ]]
                    then
                        PATH="${PATH:+"$PATH:"}$ARGA"
                    fi
                else
                    PATH="${PATH:+"$PATH:"}$ARG"
                fi
            fi
        else
            printf "Error: %s is not a directory.\n" "$ARG" >&2
        fi
    done
}
qwertyzw
источник
(1) Вы должны добавить кавычки: readlink -f "$ARG". (2) Я не знаю, почему это произошло (особенно после -d "$ARG"успешного прохождения теста), но вы можете проверить, readlinkпровалился ли он . (3) Вы, кажется, упускаете из виду readlinkосновную функцию - сопоставить имя символической ссылки с «реальным именем файла». Если (например) /binсимволическая ссылка на /bin64, то повторные вызовы pathappend /binмогут привести к произнесению PATH …:/bin64:/bin64:/bin64:/bin64:…. Вероятно, вам следует (также) проверить, есть ли новое значение $ARGв PATH.
Скотт
(1) Хорошее наблюдение, я исправил это. (2) в каком случае readlink потерпит неудачу? Предполагая, что путь правильный, и он ссылается на действительное местоположение. (3) Я не уверен, что диктует основную функцию readlink, я полагаю, что большинство (если не все?) Путей в файловой системе unix можно разделить на 2 типа ссылок, символические ссылки и жесткие ссылки. Что касается повторяющейся записи пути, вы правы, но цель моего ответа не состояла в том, чтобы добавить это (поскольку я заметил, что другие ответы уже упоминали об этом). Единственная причина, по которой я добавляю еще один ответ, состоит в том, чтобы внести что-то, что, как я думал, еще не было внесено
qwertyzw
(2) Я имею в виду, что если по крайней мере название команды подразумевает / намекает на ее назначение, то «ссылка» в readlink может относиться к жестким или программным ссылкам. Однако вы правы: man readlink говорит «readlink - печатать разрешенные символические ссылки или канонические имена файлов», и .я считаю , что в моем примере это можно считать каноническим именем файла. Верный?
qwertyzw
(1) Для людей, которые понимают символические ссылки, readlinkимя явно подразумевает его назначение - оно смотрит на символическую ссылку и считывает путь, который она содержит (то есть указывает на). (2) Ну, я сказал, что не знаю, почему не readlinkполучится. Я сделал общее замечание, что если сценарий или функция содержит несколько команд, а команда  n гарантированно потерпит крах (или вообще не имеет смысла), если команда  m не выполнена (где m  <  n ), целесообразно проверить , не завершилась ли команда m, и обработать ее каким-то образом - по крайней мере, ... (продолжение)
Скотт
(Продолжение)… прервать выполнение сценария / функции с помощью диагностики. Гипотетически readlinkможет произойти сбой, если (a) каталог был переименован или удален (другим процессом) между вашими вызовами testи readlink, или (b) если /usr/bin/readlinkбыл удален (или поврежден). (3) Вы, кажется, упускаете мою точку зрения. Я не призываю вас дублировать другие ответы; Я говорю, что, проверяя, находится ли оригинал ARG(из командной строки), уже в нем PATH, но не повторяя проверку нового ARG(вывод из readlink), ваш ответ неполон и, следовательно, неверен. … (Продолжение)
Скотт
1
function __path_add(){  
if [ -d "$1" ] ; then  
    local D=":${PATH}:";   
    [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
fi  
}  
Greenfox
источник
1

Этот способ отлично работает:

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi
Akceptor
источник
0

Мои версии менее осторожны с пустыми путями и настаивают на правильности путей и каталогов, чем некоторые, опубликованные здесь, но я нахожу большую коллекцию prepend / append / clean / unique-ify / etc. Функции оболочки должны быть полезны для манипулирования путями. Все, в их текущем состоянии, здесь: http://pastebin.com/xS9sgQsX (отзывы и улучшения очень приветствуются!)

andybuckley
источник
0

Вы можете использовать perl one liner:

appendPaths() { # append a group of paths together, leaving out redundancies
    # use as: export PATH="$(appendPaths "$PATH" "dir1" "dir2")
    # start at the end:
    #  - join all arguments with :,
    #  - split the result on :,
    #  - pick out non-empty elements which haven't been seen and which are directories,
    #  - join with :,
    #  - print
    perl -le 'print join ":", grep /\w/ && !$seen{$_}++ && -d $_, split ":", join ":", @ARGV;' "$@"
}

Вот это в bash:

addToPath() { 
    # inspired by Gordon Davisson, http://superuser.com/a/39995/208059
    # call as: addToPath dir1 dir2
    while (( "$#" > 0 )); do
    echo "Adding $1 to PATH."
    if [[ ! -d "$1" ]]; then
        echo "$1 is not a directory.";
    elif [[ ":$PATH:" == *":$1:"* ]]; then
        echo "$1 is already in the path."
    else
            export PATH="${PATH:+"$PATH:"}$1" # ${x:-defaultIfEmpty} ${x:+valueIfNotEmpty}
    fi
    shift
    done
}
dpatru
источник
0

Я немного изменил ответ Гордона Дэвиссона, чтобы использовать текущий каталог, если он не указан. Таким образом, вы можете просто сделать paddиз каталога, который вы хотите добавить в ваш путь.

padd() {
  current=`pwd`
  p=${1:-$current}
  if [ -d "$p" ] && [[ ":$PATH:" != *":$p:"* ]]; then
      PATH="$p:$PATH"
  fi
}
Торстен Лоренц
источник
0

Вы можете проверить, была ли установлена ​​пользовательская переменная, иначе установить ее, а затем добавить новые записи:

if [ "$MYPATHS" != "true" ]; then
    export MYPATHS="true"
    export PATH="$PATH:$HOME/bin:"

    # java stuff
    export JAVA_HOME="$(/usr/libexec/java_home)"
    export M2_HOME="$HOME/Applications/apache-maven-3.3.9"
    export PATH="$JAVA_HOME/bin:$M2_HOME/bin:$PATH"

    # etc...
fi

Конечно, эти записи могут быть дублированы, если они добавлены другим скриптом, например /etc/profile.

Дэвид Кеннеди
источник
0

Этот скрипт позволяет добавить в конце $PATH:

PATH=path2; add_to_PATH after path1 path2:path3
echo $PATH
path2:path1:path3

Или добавить в начале $PATH:

PATH=path2; add_to_PATH before path1 path2:path3
echo $PATH
path1:path3:path2

# Add directories to $PATH iff they're not already there
# Append directories to $PATH by default
# Based on https://unix.stackexchange.com/a/4973/143394
# and https://unix.stackexchange.com/a/217629/143394
add_to_PATH () {
  local prepend  # Prepend to path if set
  local prefix   # Temporary prepended path
  local IFS      # Avoid restoring for added laziness

  case $1 in
    after)  shift;; # Default is to append
    before) prepend=true; shift;;
  esac

  for arg; do
    IFS=: # Split argument by path separator
    for dir in $arg; do
      # Canonicalise symbolic links
      dir=$({ cd -- "$dir" && { pwd -P || pwd; } } 2>/dev/null)
      if [ -z "$dir" ]; then continue; fi  # Skip non-existent directory
      case ":$PATH:" in
        *":$dir:"*) :;; # skip - already present
        *) if [ "$prepend" ]; then
           # ${prefix:+$prefix:} will expand to "" if $prefix is empty to avoid
           # starting with a ":".  Expansion is "$prefix:" if non-empty.
            prefix=${prefix+$prefix:}$dir
          else
            PATH=$PATH:$dir  # Append by default
          fi;;
      esac
    done
  done
  [ "$prepend" ] && PATH=$prefix:$PATH
}
Том Хейл
источник
0

Вот POSIX-совместимый способ.

# USAGE: path_add [include|prepend|append] "dir1" "dir2" ...
#   prepend: add/move to beginning
#   append:  add/move to end
#   include: add to end of PATH if not already included [default]
#          that is, don't change position if already in PATH
# RETURNS:
# prepend:  dir2:dir1:OLD_PATH
# append:   OLD_PATH:dir1:dir2
# If called with no paramters, returns PATH with duplicate directories removed
path_add() {
    # use subshell to create "local" variables
    PATH="$(path_unique)"
    export PATH="$(path_add_do "$@")"
}

path_add_do() {
    case "$1" in
    'include'|'prepend'|'append') action="$1"; shift ;;
    *)                            action='include'   ;;
    esac

    path=":$PATH:" # pad to ensure full path is matched later

    for dir in "$@"; do
        #       [ -d "$dir" ] || continue # skip non-directory params

        left="${path%:$dir:*}" # remove last occurrence to end

        if [ "$path" = "$left" ]; then
            # PATH doesn't contain $dir
            [ "$action" = 'include' ] && action='append'
            right=''
        else
            right=":${path#$left:$dir:}" # remove start to last occurrence
        fi

        # construct path with $dir added
        case "$action" in
            'prepend') path=":$dir$left$right" ;;
            'append')  path="$left$right$dir:" ;;
        esac
    done

    # strip ':' pads
    path="${path#:}"
    path="${path%:}"

    # return
    printf '%s' "$path"
}

# USAGE: path_unique [path]
# path - a colon delimited list. Defaults to $PATH is not specified.
# RETURNS: `path` with duplicated directories removed
path_unique() {
    in_path=${1:-$PATH}
    path=':'

    # Wrap the while loop in '{}' to be able to access the updated `path variable
    # as the `while` loop is run in a subshell due to the piping to it.
    # https://stackoverflow.com/questions/4667509/shell-variables-set-inside-while-loop-not-visible-outside-of-it
    printf '%s\n' "$in_path" \
    | /bin/tr -s ':' '\n'    \
    | {
            while read -r dir; do
                left="${path%:$dir:*}" # remove last occurrence to end
                if [ "$path" = "$left" ]; then
                    # PATH doesn't contain $dir
                    path="$path$dir:"
                fi
            done
            # strip ':' pads
            path="${path#:}"
            path="${path%:}"
            # return
            printf '%s\n' "$path"
        }
}

Он списан из Гийого Перро-Archambault «s ответа на этот вопрос и mike511 » s ответа здесь .

ОБНОВЛЕНИЕ 2017-11-23: Исправлена ​​ошибка в @Scott

go2null
источник
Я собирался объявить это за предоставление опции командной строки для выбора между добавлением и добавлением (добавлением), по умолчанию. Но потом я подумал: это ужасно много загадочного кода без объяснения причин. (И тот факт, что у вас есть две функции, одна из которых изменяет PATH и отображает его новое значение, а другая захватывает вывод и назначает его снова в PATH , является просто ненужной сложностью.)… (Продолжение)
Скотт,
(Продолжение) ... А потом я заметил, что ссылки были неправильными. (И я не уверен, почему вы обвиняете этих парней; похоже, вы не слишком много копировали из их ответов.) И затем я заметил, что код был неправильным. Я предполагаю, что это делает хорошую работу по поддержанию правильно сформированного PATH, но нет никакой гарантии, что это уже правильно сформировано, особенно если у вас есть непросветленный /etc/profile. Каталог, который вы пытаетесь добавить в PATH, может быть уже там несколько раз , и ваш код его захлебывается. … (Продолжение)
Скотт
(Продолжение) ... Например, если вы пытаетесь предварять /a/ckк /b:/a/ck:/tr:/a/ck, вы получите /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
Скотт