Як я можу чисто додати до $ PATH?


31

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

Одна з причин цього хочу зробити так, щоб можна було внести доповнення .bashrc, які не потребують входу, а також корисніше для систем, які використовують (наприклад) lightdm, яка ніколи не викликає.profile .

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



goldi, я не знаю чому, але я бачив твій перший коментар навіть із порожнім. Але так, іменні префікси також працюють, не хвилюйтесь! Закрити інший спосіб також добре.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Гаразд, поки ви отримали моє повідомлення. Іноді таке перевернення викликає трохи хаосу, я думаю, ми побачимо, що станеться.
goldilocks

Відповіді:


35

Припустимо, що новий шлях, який ми хочемо додати:

new=/opt/bin

Тоді, використовуючи будь-яку оболонку POSIX, ми можемо перевірити, чи newє вже на шляху, і додати його, якщо його немає:

case ":${PATH:=$new}:" in
    *:"$new":*)  ;;
    *) PATH="$new:$PATH"  ;;
esac

Зверніть увагу на використання колонок. Без колонок, ми можемо подумати, що, скажімо, new=/binвже було на шляху, тому що він узгоджувався /usr/bin. Незважаючи на те, що PATH зазвичай має багато елементів, також обробляються особливі випадки нуля та один елемент у PATH. Випадок шляху з самого початку не мають елементів (будучи порожнім) обробляються при використанні ${PATH:=$new}якого призначає PATHщоб , $newякщо він порожній. Встановлення значень за замовчуванням для параметрів таким чином є характеристикою всіх оболонок POSIX: див. Розділ 2.6.2 документів POSIX .)

Функція дзвінка

Для зручності вищевказаний код можна ввести у функцію. Цю функцію можна визначити в командному рядку або, щоб вона була постійно доступною, помістити в сценарій ініціалізації вашої оболонки (для користувачів bash, що це буде ~/.bashrc):

pupdate() { case ":${PATH:=$1}:" in *:"$1":*) ;; *) PATH="$1:$PATH" ;; esac; }

Щоб скористатися цією функцією оновлення шляху, щоб додати каталог до поточного PATH:

pupdate /new/path

@hammar ОК. Я додав справу для цього.
John1024

1
Ви можете зберегти 2 відмінності у справах - пор. unix.stackexchange.com/a/40973/1131 .
maxschlepzig

3
Якщо PATHпорожній, це додасть порожній запис (тобто поточний каталог) до PATH. Я думаю, вам потрібна інша справа.
CB Bailey

2
@CharlesBailey Не інший case. Просто роби case "${PATH:=$new}". Дивіться мою власну відповідь на подібні резервні результати.
mikeserv

1
@ mc0e Я додав приклад того, як використовувати функцію оболонки, щоб приховати "шум лінії".
John1024

9

Створіть файл у /etc/profile.dвиклику, наприклад, mypath.sh(або що завгодно). Якщо ви використовуєте lightdm, переконайтеся, що це життєздатно, або скористайтеся /etc/bashrcфайлом, отриманим з того самого. Додайте до цього наступні функції:

checkPath () {
        case ":$PATH:" in
                *":$1:"*) return 1
                        ;;
        esac
        return 0;
}

# Prepend to $PATH
prependToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$a:$PATH
                fi
        done
        export PATH
}

# Append to $PATH
appendToPath () {
        for a; do
                checkPath $a
                if [ $? -eq 0 ]; then
                        PATH=$PATH:$a
                fi
        done
        export PATH
}

Речі на початку (передбачуваної) $ PATH мають перевагу над подальшим, і, навпаки, речі в кінці (додаються) будуть замінені тим, що відбувається раніше. Це означає, що якщо ваш $ PATH є, /usr/local/bin:/usr/binі gotchaв обох каталогах /usr/local/binє виконаний файл , той , який використовується, буде використаний за замовчуванням.

Тепер ви можете - у цьому ж файлі, в іншому конфігураційному файлі оболонки або з командного рядка - використовувати:

appendToPath /some/path /another/path
prependToPath /some/path /yet/another/path

Якщо це в а .bashrc, це запобігає появі значення не раз під час запуску нової оболонки. Існує обмеження в тому, що якщо ви хочете додати щось, що було попередньо (тобто перемістити шлях в межах $ PATH) або навпаки, вам доведеться зробити це самостійно.


розщеплення $PATHз IFS=:в кінцевому рахунку , більш гнучким , ніж case.
mikeserv

@mikeserv Без сумніву. Це своєрідне використання хакету для caseIMO. Я думаю, що тут awkможна було б добре використати.
goldilocks

Це хороший момент. І, як я думаю, gawkміг би безпосередньо призначити $PATH.
mikeserv

5

Ви можете це зробити так:

echo $PATH | grep /my/bin >/dev/null || PATH=$PATH:/my/bin

Примітка. Якщо ви будуєте PATH з інших змінних, переконайтеся, що вони не порожні, оскільки багато оболонок інтерпретують "як". " .


+1 Згідно з довідковою сторінкою -qPOSIX потрібна для grep, але я не знаю, чи це означає, що є ще деякі (не POSIX) грейпи, які не мають.
goldilocks

1
зауважте, що малюнок греппу занадто широкий. Подумайте про використання egrep -q "(^ |:) / my / bin (: | \ $)" замість grep / my / bin> / dev / null. З цією модифікацією ваше рішення є правильним, і я вважаю, що це рішення зручніше для читання, ніж відповідь на даний момент від @ john1024. Зауважте, що я використовував подвійні лапки, тому ви використовуєте змінну підстановку замість/my/bin
mc0e

5

Важливою частиною коду є перевірка, чи PATHмістить конкретний шлях:

printf '%s' ":${PATH}:" | grep -Fq ":${my_path}:"

Тобто переконайтесь, що кожен шлях у PATHрозділеному з обох боків PATHроздільником ( :), а потім перевірте ( -q), чи існує буква буква ( -F), що складається з PATHроздільника, вашого шляху та іншого PATHроздільника. Якщо це не так, ви можете сміливо додати шлях:

if ! printf '%s' ":${PATH-}:" | grep -Fq ":${my_path-}:"
then
    PATH="${PATH-}:${my_path-}"
fi

Це повинно бути сумісним з POSIX і має працювати з будь-яким контуром, що не містить символу нової лінії. Це складніше, якщо ви хочете, щоб він працював із шляхами, що містять новий рядок, одночасно сумісний з POSIX, але якщо у вас є grepпідтримка, -zви можете використовувати це.


4

Я цю функцію ~/.profileдовгі роки виконував із собою в різних файлах. Я думаю, що це написав систематик в лабораторії, в якій я працював, але я не впевнений. У будь-якому випадку він схожий на підхід Голділок, але трохи інший:

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

Отже, щоб додати новий каталог до початку PATH:

pathmunge /new/path

і до кінця:

pathmunge /new/path after

Це працює для мене! Але я помінявся на логіку, щоб поставити її після за замовчуванням, і перекрив "до". :)
Кевін Паулі

pathmunge є частиною дистрибуції / тощо / профілю linux centos, має параметр до і після. Я не бачу цього в моєму останньому ubuntu 16.
Kemin Zhou

Здається, працює нормально на macOS 10.12 після /bin/grep->grep
Бен Крізі

4

ОНОВЛЕННЯ:

Я помітив, що ваша власна відповідь мала окрему функцію для додавання чи попереднього прийому до $PATH. Ідея мені сподобалась. Тому я додав трохи обробки аргументів. Я також правильно _розмістив його назви:

_path_assign() { oFS=$IFS ; IFS=: ; add=$* ; unset P A ; A=
    set -- ${PATH:=$1} ; for p in $add ; do {
        [ -z "${p%-[AP]}" ] && { unset P A
                eval ${p#-}= ; continue ; }
        for d ; do [ -z "${d%"$p"}" ] && break
        done ; } || set -- ${P+$p} $* ${A+$p}
        done ; export PATH="$*" ; IFS=$oFS
}

% PATH=/usr/bin:/usr/yes/bin
% _path_assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/bin/nope \
    -P \
    /usr/nope/bin \
    /usr/bin \
    -A \
    /nope/usr/bin \
    /usr/nope/bin

% echo $PATH

ВИХІД:

/usr/nope/bin:/usr/bin:/usr/yes/bin:/usr/bin/nope:/nope/usr/bin

За замовчуванням він буде -Aзалежати $PATH, але ви можете змінити цю поведінку на -Pвідшкодування, додавши -Pде-небудь у свій список аргументів. Ви можете переключити його в -Aрежим розробки, передавши його-A раз.

БЕЗПЕЧНИЙ ВЕСЬ

У більшості випадків я рекомендую людям уникати будь-якого використання eval. Але це, я думаю, виділяється як приклад його використання на благо. У цьому випадку єдиним твердженням, яке eval можна побачити, є P=або A=. Значення його аргументів суворо перевіряються до того, як воно називається. Це для чого eval .

assign() { oFS=$IFS ; IFS=: ; add=$* 
    set -- ${PATH:=$1} ; for p in $add ; do { 
        for d ; do [ -z "${d%"$p"}" ] && break 
        done ; } || set -- $* $p ; done
    PATH="$*" ; IFS=$oFS
}

Це сприйме стільки аргументів, скільки ви їх наводите, і додасте кожен $PATHлише один раз і лише якщо його ще немає $PATH. Він використовує лише повністю портативний оболонку-скрипт POSIX, покладається лише на вбудовані оболонки, і це дуже швидко.

% PATH=/usr/bin:/usr/yes/bin
% assign \
    /usr/bin \
    /usr/yes/bin \
    /usr/nope/bin \
    /usr/bin \
    /nope/usr/bin \
    /usr/nope/bin

% echo "$PATH"
> /usr/bin:/usr/yes/bin:/usr/nope/bin:/nope/usr/bin

@ TAFKA'goldilocks 'див. Оновлення тут - ви мене надихнули.
mikeserv

+1 З цікавості (можливо, це було б добре окреме запитання і відповіді), звідки _походить думка про те, що функції префіксації оболонки роблять їх "належними місцями"? В інших мовах це зазвичай вказує на внутрішню глобальну функцію (тобто таку, яка повинна бути глобальною, але не призначена для використання зовнішньо як частина API). Мої імена, безумовно, не є великим вибором, але, як мені здається, просто використання _не вирішує проблем зіткнення - краще було б застосувати фактичну область імен, наприклад. mikeserv_path_assign().
goldilocks

@ TAFKA'goldilocks '- краще було б ще більше конкретизуватись з ним, але чим довше назва стає менш зручним, ніж її використання. Але якщо у вас є відповідні виконувані бінарні файли з префіксом, _вам потрібно переключити менеджерів пакетів. У будь-якому випадку, це, по суті, просто aa "глобальна, внутрішня, функціональна" - це глобальна для кожної оболонки, викликаної з оболонки, в якій вона оголошена, і це лише трохи інтерпретованого мовного скрипту, що висить у пам'яті інтерпретатора . unix.stackexchange.com/questions/120528/…
mikeserv

Ви не можете unset a(або еквівалент) в кінці профілю?
sourcejedi

0

Ось! 12-лінійна промислова потужність ... технічно функціональна оболонка з базовою та zsh-технологією, яка віддано любить ваш вибір ~/.bashrcабо ~/.zshrcсценарій запуску на вибір:

# void +path.append(str dirname, ...)
#
# Append each passed existing directory to the current user's ${PATH} in a
# safe manner silently ignoring:
#
# * Relative directories (i.e., *NOT* prefixed by the directory separator).
# * Duplicate directories (i.e., already listed in the current ${PATH}).
# * Nonextant directories.
+path.append() {
    # For each passed dirname...
    local dirname
    for   dirname; do
        # Strip the trailing directory separator if any from this dirname,
        # reducing this dirname to the canonical form expected by the
        # test for uniqueness performed below.
        dirname="${dirname%/}"

        # If this dirname is either relative, duplicate, or nonextant, then
        # silently ignore this dirname and continue to the next. Note that the
        # extancy test is the least performant test and hence deferred.
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue

        # Else, this is an existing absolute unique dirname. In this case,
        # append this dirname to the current ${PATH}.
        PATH="${PATH}:${dirname}"
    done

    # Strip an erroneously leading delimiter from the current ${PATH} if any,
    # a common edge case when the initial ${PATH} is the empty string.
    PATH="${PATH#:}"

    # Export the current ${PATH} to subprocesses. Although system-wide scripts
    # already export the ${PATH} by default on most systems, "Bother free is
    # the way to be."
    export PATH
}

Підготуй себе до миттєвої слави. Тоді, а не робити це і бажаючи сподіватися на краще:

export PATH=$PATH:~/opt/bin:~/the/black/goat/of/the/woods/with/a/thousand/young

Зробіть це замість цього, і вам гарантується отримати найкраще, хочете ви цього хотіли чи ні:

+path.append ~/opt/bin ~/the/black/goat/of/the/woods/with/a/thousand/young

Дуже добре, визначте "Найкраще".

Безпечне додавання та припередження на поточний ${PATH}не є тривіальною справою, якою це зазвичай робиться. Незважаючи на зручність і, здавалося б, розважливість, однолінійки форми export PATH=$PATH:~/opt/binвикликають диявольські ускладнення:

  • Випадково відносні прізвища (наприклад, export PATH=$PATH:opt/bin). Хоча bashі zshмовчки приймають і, в основному, ігнорують відносні прізвища в більшості випадків, відносні дирікаменти з префіксом або, hабо t(можливо, іншими недобрими персонажами) призводять до того, що вони ганебно понівечать семінарський шедевр ала Масакі Кобаяші 1962 року Харакірі :

    # Don't try this at home. You will feel great pain.
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:harakiri && echo $PATH
    /usr/local/bin:/usr/bin:arakiri
    $ PATH='/usr/local/bin:/usr/bin:/bin' && export PATH=$PATH:tanuki/yokai && echo $PATH
    binanuki/yokai   # Congratulations. Your system is now face-up in the gutter.
  • Випадково копіюйте прізвища. Незважаючи на те, що дублюючі ${PATH}дирижаблі в основному нешкідливі, вони також небажані, громіздкі, м'яко неефективні, перешкоджають налагодженню та сприяють зносу приводу - подібного роду відповідь. Незважаючи на те , що жорсткі диски у стилі NAND ( звичайно ) не захищені від зчитування, жорсткі диски не є. Непотрібний доступ до файлової системи для кожної спроби команди передбачає непотрібне знос головної шапки при одному темпі. Дублікати особливо неприйнятні, коли викликати вкладені оболонки в вкладені підпроцеси, і в цей момент, здавалося б, нешкідливі одношаровики, як export PATH=$PATH:~/watшвидко вибухають у подібне до Сьомого кола ${PATH}Пекла PATH=/usr/local/bin:/usr/bin:/bin:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat:/home/leycec/wat. Тільки Beelzebubba може допомогти вам, якщо ви додасте до цього додаткові імена. (Не дозволяйте цього статися з вашими дорогоцінними дітьми. )

  • Випадково відсутні прізвища. Знову ж таки, хоча відсутні ${PATH}дирікаменти в основному нешкідливі, вони також, як правило, небажані, громіздкі, м'яко неефективні, перешкоджають налагодженню та сприяють зносу приводу.

Ерго, дружелюбна автоматизація, як функція оболонки, визначена вище. Ми повинні врятувати себе від себе.

Але ... Чому "+ path.append ()"? Чому б не просто append_path ()?

Для disambiguity (наприклад, з зовнішніми командами в поточній ${PATH}або всій системі функцій оболонки , визначених в іншому місці), що визначається користувач функція оболонки ідеально приставка або суфікс з унікальними підрядками , підтримуваних bashі , zshале іншим чином заборонена для стандартних команд basenames - як, скажімо, +.

Гей. Це працює. Не судіть мене.

Але ... Чому "+ path.append ()"? Чому б не "+ path.prepend ()"?

Оскільки приєднання до струму ${PATH}є більш безпечним, ніж попередження струму ${PATH}, всі речі рівні, якими вони ніколи не є. Перевизначення загальносистемних команд з конкретними для користувача командами може бути антисанітарним у кращому випадку та божевільним. Наприклад, під Linux, додатки нижче за течією зазвичай очікують варіантів команд GNU, а не спеціальні нестандартні похідні або альтернативи.

Однак, для цього абсолютно є дійсні випадки використання. Визначення еквівалентної +path.prepend()функції тривіальне. Не враховує туманність, за його та її спільний розум:

+path.prepend() {
    local dirname
    for dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${dirname}:${PATH}"
    done
    PATH="${PATH%:}"
    export PATH
}

Але ... Чому б не Жиль?

Прийнята відповідь Жиля в іншому місці є вражаючо оптимальною в загальному випадку як "додаток агностичного ідентифікаційного оболонки" . У загальному випадку bashі zshз НЕ небажаних симлінк, однак, втрати продуктивності потрібно зробити так засмучує на приготування овочевого пюре Gentoo в мені. Навіть за наявності небажаних символьних посилань, дискусійним є питання про те, чи варто роздрібнення однієї підзарядки на add_to_PATH()аргумент потенційної вставки дублікатів символьних посилань .

Для випадків суворого використання, які вимагають усунення навіть дублікатів zshсимпосилання , це специфічний варіант робить це за допомогою ефективних вбудованих, а не неефективних вил:

+path.append() {
    local dirname
    for   dirname in "${@}"; do
        dirname="${dirname%/}"
        [[ "${dirname:0:1}" == '/' &&
           ":${PATH}:" != *":${dirname:A}:"* &&
           -d "${dirname}" ]] || continue
        PATH="${PATH}:${dirname}"
    done
    PATH="${PATH#:}"
    export PATH
}

Зауважте, *":${dirname:A}:"*а не *":${dirname}:"*оригінал. :Aє дивовижним, на zshжаль, відсутність у більшості інших оболонок - у тому числі bash. Цитувати man zshexpn:

A : Перетворіть ім'я файлу в абсолютний шлях, як це aробить модифікатор, а потім передайте результат через функцію realpath(3)бібліотеки для вирішення символічних посилань. Примітка. У системах, які не мають realpath(3)бібліотечної функції, символьні посилання не вирішуються, тому на цих системах aі Aє рівнозначними.

Ніяких додаткових питань.

Ласкаво просимо. Насолоджуйтесь безпечним обстрілом. Ви зараз на це заслужили.


0

Ось моя версія функціонального програмування.

  • Працює для будь-якої *PATHзмінної товстої кишки , не тільки PATH.
  • Не має доступу до глобальної держави
  • Працює лише з / над даними непорушних даних
  • Виробляє один вихід
  • Побічних ефектів немає
  • Запам'ятовується (в принципі)

Також примітно:

  • Агностик щодо exportінг; що залишається абоненту (див. приклади)
  • Чистий bash; відсутність роздрібнення
path_add () {
  # $ 1: Елемент, який слід переконатись, знаходиться в заданому рядку шляху рівно один раз
  # $ 2: Існуюче значення рядка шляху ("$ PATH", а не "PATH")
  # $ 3 (необов’язково, що завгодно): якщо вказано, додайте $ 1; в іншому випадку, прип
  #
  # Приклади:
  # $ експорт PATH = $ (path_add '/ opt / bin' "$ PATH")
  # $ CDPATH = $ (path_add '/ Музика' "$ CDPATH" at_end)

  локальна -r вже_презентація = "(^ |:) $ {1} ($ | :)"
  if [["$ 2" = ~ $ вже_презентація]]; потім
    відлуння "$ 2"
  elif [[$ # == 3]]; потім
    відлуння "$ {2}: $ {1}"
  ще
    відлуння "$ {1}: $ {2}"
  фі
}

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" ] && [ "$prefix" != "" ] && PATH=$prefix:$PATH
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.