Добавить каталог в $ 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


Если вы сформулируете проблему как «только добавление, если ее там еще нет», вы будете грубо удивлены, когда придет день, когда важно, чтобы вставленный элемент находился в начале, но он там не заканчивается. Лучшим подходом было бы вставить элемент, а затем удалить дубликаты, поэтому, если новая запись уже была там, она будет эффективно перемещена в начало.
— Дон Хэтч

Ответы:


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/

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
}

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
}

(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  
}  

1

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

if [[ ":$PATH:" != *":/new-directory:"* ]]; then PATH=${PATH}:/new-directory; fi

0

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


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
}

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


Я собирался объявить это за предоставление опции командной строки для выбора между добавлением и добавлением (добавлением), по умолчанию. Но потом я подумал: это ужасно много загадочного кода без объяснения причин. (И тот факт, что у вас есть две функции, одна из которых изменяет PATH и отображает его новое значение, а другая захватывает вывод и назначает его снова в PATH , является просто ненужной сложностью.)… (Продолжение)
— Скотт,

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

(Продолжение) ... Например, если вы пытаетесь предварять /a/ckк /b:/a/ck:/tr:/a/ck, вы получите /a/ck:/b:/a/ck:/tr:/tr:/a/ck.
— Скотт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.