Какой самый элегантный способ удалить путь из переменной $ PATH в Bash?

Или, в более общем плане, как мне удалить элемент из списка, разделенного двоеточиями, в переменной среды Bash?

Я думал, что видел простой способ сделать это много лет назад, используя более продвинутые формы расширения переменных Bash, но в таком случае я потерял его из виду. Быстрый поиск в Google дал на удивление мало релевантных результатов, и ни один из них я бы не назвал «простым» или «элегантным». Например, два метода с использованием sed и awk соответственно:

PATH=$(echo $PATH | sed -e 's;:\?/home/user/bin;;' -e 's;/home/user/bin:\?;;')
PATH=!(awk -F: '{for(i=1;i<=NF;i++){if(!($i in a)){a[$i];printf s$i;s=":"}}}'<<<$PATH)

Не существует ничего прямого? Есть ли что-нибудь аналогичное функции split () в Bash?

Обновление:
Похоже, мне нужно извиниться за умышленно расплывчатый вопрос; Меня меньше интересовало решение конкретного варианта использования, чем спровоцировать хорошее обсуждение. К счастью, я понял!

Здесь есть несколько очень умных приемов. В конце концов, я добавил в свой набор инструментов следующие три функции. Магия происходит в path_remove, который во многом основан на умном использовании Мартином Йорком переменной RS awk.

path_append ()  { path_remove $1; export PATH="$PATH:$1"; }
path_prepend () { path_remove $1; export PATH="$1:$PATH"; }
path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; }

Единственная реальная проблема - это использование sed для удаления завершающего двоеточия. Однако, учитывая, насколько проста остальная часть решения Мартина, я вполне готов смириться с этим!


Связанный вопрос: Как управлять элементами $ PATH в сценариях оболочки?


person Ben Blank    schedule 15.12.2008    source источник
comment
Для любой переменной: WORK=`echo -n ${1} | awk -v RS=: -v ORS=: '$0 != "'${3}'"' | sed 's/:$//'`; eval "export ${2}=${WORK}", но вы должны называть ее func $VAR VAR pattern (на основе @ martin-york и @ andrew-aylett)   -  person vesperto    schedule 12.11.2019


Ответы (33)


Минута с awk:

# Strip all paths with SDE in them.
#
export PATH=`echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}'`

Изменить: это ответ на комментарии ниже:

$ export a="/a/b/c/d/e:/a/b/c/d/g/k/i:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i"
$ echo ${a}
/a/b/c/d/e:/a/b/c/d/f:/a/b/c/g:/a/b/c/d/g/i

## Remove multiple (any directory with a: all of them)
$ echo ${a} | awk -v RS=: -v ORS=: '/a/ {next} {print}'
## Works fine all removed

## Remove multiple including last two: (any directory with g)
$ echo ${a} | awk -v RS=: -v ORS=: '/g/ {next} {print}'
/a/b/c/d/e:/a/b/c/d/f:
## Works fine: Again!

Изменить в ответ на проблему безопасности: (это не имеет отношения к вопросу)

export PATH=$(echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}' | sed 's/:*$//')

Это удалит любые конечные двоеточия, оставленные путем удаления последних записей, что фактически добавит . к вашему пути.

person Martin York    schedule 16.12.2008
comment
Ошибка при попытке удалить последний элемент или несколько элементов: в первом случае добавляется текущий каталог (как пустая строка; потенциальная дыра в безопасности), во втором случае он добавляет `` как элемент пути. - person Fred Foo; 13.03.2011
comment
@larsmans: У меня отлично работает. Примечание. Пустой - это не то же самое, что текущий каталог ./ - person Martin York; 13.03.2011
comment
Пустая строка как член переменной PATH действительно, как специальное правило, обозначает текущий каталог во всех оболочках Unix, начиная с версии V7 Unix 1979 года. Это все еще используется в bash. Проверьте руководство или попробуйте сами. - person Fred Foo; 14.03.2011
comment
@Martin: POSIX не требует такого поведения, но документирует и разрешает его: pubs .opengroup.org / onlinepubs / 9699919799 / basedefs / - person Fred Foo; 14.03.2011
comment
@larsmans: Интересно. Хотя это легко исправить. Хотя любой, кто имел какие-либо опасения по поводу безопасности в связи с этим вопросом, мог легко задать его как отдельный вопрос, а не мутить воду в этом вопросе не относящимися к делу пунктами. В конце концов, я по-прежнему предпочитаю свой альтернативный ответ - использовать vi для выполнения этой работы. - person Martin York; 14.03.2011
comment
При удалении последнего элемента возникает еще более тонкая проблема: похоже, что awk добавляет нулевой байт в конец строки. Это означает, что если вы позже добавите другой каталог в PATH, он фактически не будет просматриваться. - person sschuberth; 25.10.2012
comment
Можете ли вы объяснить awk функциональность? - person Jacktose; 17.04.2021
comment
@Jacktose RS = ›Разделитель записей. Так что разделите строку на ":", чтобы применить правила против. ORS = ›Разделитель выходной записи. Поэтому, когда мы вернем его обратно, снова разделите записи ":". Правило 1: /SDE/ {next} Если запись содержит / SDE /, проигнорируйте запись и перейдите к следующей записи. Правило 2: {print} Любая запись, не соответствующая правилу 1, будет соответствовать правилу 2 и будет просто напечатана. В результате удаляются все пути с / SDE / в них из переменной среды PATH. Не идеально, как указано в комментариях, но будет охватывать большинство случаев. - person Martin York; 17.04.2021

Мой грязный хак:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
person Martin York    schedule 16.12.2008
comment
Когда наиболее очевидным решением является де -автоматизация процесса, это никогда не является хорошим знаком. :-D - person Ben Blank; 17.12.2008
comment
НАСТОЛЬКО безопаснее, чем элегантные альтернативы. - person Jortstek; 05.03.2020

Поскольку большая проблема с заменой - это конечные варианты, как насчет того, чтобы конечные варианты не отличались от других вариантов? Если бы путь уже имел двоеточия в начале и в конце, мы могли бы просто найти нужную строку, заключенную в двоеточия. Как бы то ни было, мы можем легко добавить эти двоеточия и впоследствии удалить их.

# PATH => /bin:/opt/a dir/bin:/sbin
WORK=:$PATH:
# WORK => :/bin:/opt/a dir/bin:/sbin:
REMOVE='/opt/a dir/bin'
WORK=${WORK/:$REMOVE:/:}
# WORK => :/bin:/sbin:
WORK=${WORK%:}
WORK=${WORK#:}
PATH=$WORK
# PATH => /bin:/sbin

Чистый баш :).

person Andrew Aylett    schedule 21.01.2010
comment
Я бы добавил этот учебный раздел для дополнительной глазури: tldp.org/LDP/ абс / html / строка-манипуляция.html - person Cyber Oliveira; 08.10.2011
comment
Я использовал это, потому что это выглядело как простейшее решение. Это было очень быстро и легко, и вы можете легко проверить свою работу с помощью echo $ WORK прямо перед последней строкой, где вы действительно меняете переменную PATH. - person Phil Gran; 21.08.2012
comment
Совершенно маленькая жемчужина. Именно то, что я пытался сделать, когда нашел этот пост. -Спасибо Андрей! Кстати: может быть, вы хотите добавить двойные кавычки вокруг: $ PATH :, на всякий случай, если он должен содержать пробелы (то же самое относительно / usr / bin) и последнюю строку $ WORK. - person ; 09.12.2013
comment
Спасибо, @ PacMan-- :). Я почти уверен (только что попробовал) вам не нужны пробелы для присвоений WORK и PATH, поскольку расширение переменной происходит после того, как строка разбита на разделы для назначения переменных и выполнения команд. REMOVE может потребоваться заключить в кавычки, или вы можете просто вставить свою строку прямо в замену, если это константа. - person Andrew Aylett; 10.12.2013
comment
Я всегда запутывался, когда строки сохранялись, поэтому я начал всегда заключать строки в двойные кавычки. Еще раз спасибо за разъяснение. :) - person ; 11.12.2013

Вот самое простое решение, которое я могу придумать:

#!/bin/bash
IFS=:
# convert it to an array
t=($PATH)
unset IFS
# perform any array operations to remove elements from the array
t=(${t[@]%%*usr*})
IFS=:
# output the new array
echo "${t[*]}"

В приведенном выше примере будет удален любой элемент в $ PATH, содержащий «usr». Вы можете заменить «* usr *» на «/ home / user / bin», чтобы удалить только этот элемент.

обновление за sschuberth

Хотя я считаю, что пробелы в $PATH - это ужасная идея, вот решение, которое с этим справится:

PATH=$(IFS=':';t=($PATH);n=${#t[*]};a=();for ((i=0;i<n;i++)); do p="${t[i]%%*usr*}"; [ "${p}" ] && a[i]="${p}"; done;echo "${a[*]}");

or

IFS=':'
t=($PATH)
n=${#t[*]}
a=()
for ((i=0;i<n;i++)); do
  p="${t[i]%%*usr*}"
  [ "${p}" ] && a[i]="${p}"
done
echo "${a[*]}"
person nicerobot    schedule 16.12.2008
comment
Как один лайнер: PATH = $ (IFS = ':'; t = ($ PATH); unset IFS; t = ($ {t [@] %% * usr *}); IFS = ':'; echo $ { t [*]}); - person nicerobot; 16.12.2008
comment
Это решение не работает с путями в PATH, содержащими пробелы; он заменяет их двоеточиями. - person sschuberth; 28.10.2012

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

export PATH=$(p=$(echo $PATH | tr ":" "\n" | grep -v "/cygwin/" | tr "\n" ":"); echo ${p%:})

Лично мне это легко читать / понимать, и в нем используются только общие команды вместо использования awk.

person sschuberth    schedule 30.10.2012
comment
... и если вы хотите что-то, что может справиться даже с символами новой строки в именах файлов, вы можете использовать это: export PATH=$(p=$(echo $PATH | tr ":" "\0" | grep -v -z "/cygwin/" | tr "\0" ":"); echo ${p%:}) (хотя, возможно, вы можете спросить себя, зачем вам это нужно, если вы это сделаете :)) - person ehdr; 14.01.2015
comment
Это удалит частичные совпадения, что, вероятно, не то, что вам нужно; Я бы использовал grep -v "^/path/to/remove\$" или grep -v -x "/path/to/remove" - person ShadSterling; 23.01.2016
comment
Прекрасное решение, но неужели вы думаете, что tr встречается чаще, чем awk? ;) - person K.-Michael Aye; 25.05.2016
comment
Абсолютно. Легкие среды, такие как Git Bash в Windows, скорее поставляются с простым инструментом, таким как tr, а не с интерпретатором, например awk. - person sschuberth; 25.05.2016
comment
@ehdr: вам нужно заменить echo "..." на printf "%s" "...", чтобы он работал на таких путях, как -e и подобных. См. stackoverflow.com/a/49418406/102441 - person Eric; 08.10.2018

Вот решение, которое:

  • чистый Баш,
  • не вызывает другие процессы (например, sed или awk),
  • не меняется IFS,
  • не разветвляет суб-оболочку,
  • обрабатывает пути с пробелами и
  • удаляет все вхождения аргумента в PATH.

    removeFromPath() {
       local p d
       p=":$1:"
       d=":$PATH:"
       d=${d//$p/:}
       d=${d/#:/}
       PATH=${d/%:/}
    }
person robinbb    schedule 20.03.2015
comment
Мне нравится это решение. Может быть, сделать имена переменных более наглядными? - person Anukool; 11.06.2015
comment
очень хорошо. Однако не работает для сегментов пути, содержащих звездочку (*). Иногда попадают туда случайно. - person Jörg; 14.02.2019

function __path_remove () {
local D = ": $ {PATH}:";
["$ {D /: $ 1: /:}"! = "$ D"] && PATH = "$ {D /: $ 1: /:} ";
PATH =" $ {PATH / #: /} ";
экспорт PATH =" $ {PATH /%: /} ";
}

Выкопал из моего файла .bashrc. Когда вы играете с PATH, и он теряется, awk / sed / grep становится недоступным :-)

person GreenFox    schedule 13.08.2012
comment
Это очень хороший момент. (Мне никогда не нравилось запускать внешние утилиты для таких простых вещей). - person ; 11.12.2013

Лучший вариант чистого bash, который я нашел до сих пор, следующий:

function path_remove {
  PATH=${PATH/":$1"/} # delete any instances in the middle or at the end
  PATH=${PATH/"$1:"/} # delete any instances at the beginning
}

Это основано на не совсем правильном ответе на Добавить каталог в $ PATH, если его еще нет на Superuser.

person Mark Booth    schedule 25.10.2013
comment
Это тоже неплохо. Я это тестировал. Если в PATH есть повторяющийся путь (например, два совершенно одинаковых), то удаляется только один из них. Вы также можете сделать его однострочным: removePath () { PATH=${PATH/":$1"/}; PATH=${PATH/"$1:"/}; } - person ; 11.12.2013
comment
Это решение не работает, если $PATH содержит подпапку целевого (т.е. подлежащего удалению) пути. Например: a:abc/def/bin:b - ›a/bin:b, когда нужно удалить abc/def. - person Robin Hsu; 14.04.2017

Я только что использовал функции из дистрибутива bash, которые, по-видимому, существуют с 1991 года. Они все еще находятся в пакете bash-docs в Fedora и использовались в /etc/profile, но не более ...

$ rpm -ql bash-doc |grep pathfunc
/usr/share/doc/bash-4.2.20/examples/functions/pathfuncs
$ cat $(!!)
cat $(rpm -ql bash-doc |grep pathfunc)
#From: "Simon J. Gerraty" <[email protected]>
#Message-Id: <[email protected]>
#Subject: Re: a shell idea?
#Date: Mon, 09 Oct 1995 21:30:20 +1000


# NAME:
#       add_path.sh - add dir to path
#
# DESCRIPTION:
#       These functions originated in /etc/profile and ksh.kshrc, but
#       are more useful in a separate file.
#
# SEE ALSO:
#       /etc/profile
#
# AUTHOR:
#       Simon J. Gerraty <[email protected]>

#       @(#)Copyright (c) 1991 Simon J. Gerraty
#
#       This file is provided in the hope that it will
#       be of use.  There is absolutely NO WARRANTY.
#       Permission to copy, redistribute or otherwise
#       use this file is hereby granted provided that
#       the above copyright notice and this notice are
#       left intact.

# is $1 missing from $2 (or PATH) ?
no_path() {
        eval "case :\$${2-PATH}: in *:$1:*) return 1;; *) return 0;; esac"
}
# if $1 exists and is not in path, append it
add_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="\$${2:-PATH}:$1"
}
# if $1 exists and is not in path, prepend it
pre_path () {
  [ -d ${1:-.} ] && no_path $* && eval ${2:-PATH}="$1:\$${2:-PATH}"
}
# if $1 is in path, remove it
del_path () {
  no_path $* || eval ${2:-PATH}=`eval echo :'$'${2:-PATH}: |
    sed -e "s;:$1:;:;g" -e "s;^:;;" -e "s;:\$;;"`
}
person Mr. Wacky    schedule 08.03.2012

Я написал ответ на этот здесь (тоже с использованием awk). Но я не уверен, что это то, что вы ищете? Мне, по крайней мере, кажется ясным, что он делает, вместо того, чтобы пытаться уместиться в одну строку. Однако для простого лайнера, который удаляет только вещи, я рекомендую

echo $PATH | tr ':' '\n' | awk '$0 != "/bin"' | paste -sd:

Замена

echo $PATH | tr ':' '\n' | 
    awk '$0 != "/bin"; $0 == "/bin" { print "/bar" }' | paste -sd:

или (короче, но менее читаемо)

echo $PATH | tr ':' '\n' | awk '$0 == "/bin" { print "/bar"; next } 1' | paste -sd:

В любом случае, для того же вопроса и множества полезных ответов см. здесь.

person Johannes Schaub - litb    schedule 16.12.2008
comment
И если вы хотите удалить строки, содержащие частичную строку, используйте awk '$0 !~ "/bin"'. Т.е. оставьте строки, не содержащие '/ bin', с помощью оператора awk !~. - person thoni56; 05.02.2019

Что ж, в bash, поскольку он поддерживает регулярное выражение, я бы просто сделал:

PATH=${PATH/:\/home\/user\/bin/}
person mat    schedule 15.12.2008
comment
Разве это не просто расширение имени пути, а не регулярные выражения? - person dreamlax; 16.12.2008
comment
Хотя bash поддерживает регулярные выражения (начиная с bash 3), это не пример этого, это подстановка переменных. - person Robert Gamble; 16.12.2008
comment
Это расширение переменных шаблона, и у решения есть несколько проблем. 1) он не будет соответствовать первому элементу. 2) он будет соответствовать всему, что начинается с / home / user / bin, а не только / home / user / bin. 3) требует экранирования специальных символов. В лучшем случае я бы сказал, что это неполный пример. - person nicerobot; 16.12.2008

Мне нравятся три функции, показанные в обновлении @ BenBlank его исходного вопроса. Чтобы обобщить их, я использую форму с двумя аргументами, которая позволяет мне установить PATH или любую другую переменную среды, которую я хочу:

path_append ()  { path_remove $1 $2; export $1="${!1}:$2"; }
path_prepend () { path_remove $1 $2; export $1="$2:${!1}"; }
path_remove ()  { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }

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

path_prepend PATH /usr/local/bin
path_append PERL5LIB "$DEVELOPMENT_HOME/p5/src/perlmods"

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

person Cary Millsap    schedule 05.01.2016

Какой самый элегантный способ удалить путь из переменной $ PATH в Bash?

Что может быть элегантнее awk?

path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`; 

Python! Это более удобочитаемое и удобное в обслуживании решение, и его легко проверить, чтобы убедиться, что оно действительно делает то, что вы хотите.

Скажем, вы хотите удалить первый элемент пути?

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"

(Вместо конвейерной передачи из echo, os.getenv['PATH'] будет немного короче и даст тот же результат, что и выше, но я беспокоюсь, что Python может что-то сделать с этой переменной среды, поэтому, вероятно, лучше всего передать ее напрямую из среды, в которой вы заботиться.)

Аналогично удалить с конца:

PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"

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

strip_path_first () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"
}

strip_path_last () {
    PATH="$(echo "$PATH" | 
    python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"
}
person Aaron Hall    schedule 10.07.2016

Да, например, если поставить двоеточие в конце PATH, удаление пути станет менее неуклюжим и подверженным ошибкам.

path_remove ()  { 
   declare i newPATH
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      #echo ${@:${i}:1}
      newPATH="${newPATH//${@:${i}:1}:/}" 
   done
   export PATH="${newPATH%:}" 
   return 0; 
} 

path_remove_all ()  {
   declare i newPATH
   shopt -s extglob
   newPATH="${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//+(${@:${i}:1})*([^:]):/}" 
      #newPATH="${newPATH//+(${@:${i}:1})*([^:])+(:)/}" 
   done
   shopt -u extglob 
   export PATH="${newPATH%:}" 
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
person cyrill    schedule 21.01.2010

Если вас беспокоит удаление дубликатов в $ PATH, то, IMHO, самым элегантным способом было бы не добавлять их в первую очередь. В 1 строке:

if ! $( echo "$PATH" | tr ":" "\n" | grep -qx "$folder" ) ; then PATH=$PATH:$folder ; fi

$ folder можно заменить чем угодно и может содержать пробелы ("/ home / user / my documents")

person MestreLion    schedule 19.02.2011

Самое элегантное решение на чистом bash, которое я нашел на сегодняшний день:

pathrm () {                                                                      
  local IFS=':'                                                                  
  local newpath                                                                  
  local dir                                                                      
  local pathvar=${2:-PATH}                                                       
  for dir in ${!pathvar} ; do                                                    
    if [ "$dir" != "$1" ] ; then                                                 
      newpath=${newpath:+$newpath:}$dir                                          
    fi                                                                           
  done                                                                           
  export $pathvar="$newpath"                                                        
}

pathprepend () {                                                                 
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="$1${!pathvar:+:${!pathvar}}"                                  
}

pathappend () {                                                                    
  pathrm $1 $2                                                                   
  local pathvar=${2:-PATH}                                                       
  export $pathvar="${!pathvar:+${!pathvar}:}$1"                                  
} 
person TriangleTodd    schedule 07.04.2012

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

rm_from_path() {
  pattern="${1}"
  dir=''
  [ -d "${pattern}" ] && dir="$(cd ${pattern} && pwd)"  # resolve to absolute path

  new_path=''
  IFS0=${IFS}
  IFS=':'
  for segment in ${PATH}; do
    if [[ ${segment} == ${pattern} ]]; then             # string match
      continue
    elif [[ -n ${dir} && -d ${segment} ]]; then
      segment="$(cd ${segment} && pwd)"                 # resolve to absolute path
      if [[ ${segment} == ${dir} ]]; then               # logical directory match
        continue
      fi
    fi
    new_path="${new_path}${IFS}${segment}"
  done
  new_path="${new_path/#${IFS}/}"                       # remove leading colon, if any
  IFS=${IFS0}

  export PATH=${new_path}
}

Контрольная работа:

$ mkdir -p ~/foo/bar/baz ~/foo/bar/bif ~/foo/boo/bang
$ PATH0=${PATH}
$ PATH=~/foo/bar/baz/.././../boo/././../bar:${PATH}  # add dir with special names
$ rm_from_path ~/foo/boo/../bar/.  # remove same dir with different special names
$ [ ${PATH} == ${PATH0} ] && echo 'PASS' || echo 'FAIL'
person jwfearn    schedule 22.09.2014
comment
плюс один за нестандартный - person Martin York; 23.08.2017

Linux с нуля определяет три функции Bash в /etc/profile:

# Functions to help us manage paths.  Second argument is the name of the
# path variable to be modified (default: PATH)
pathremove () {
        local IFS=':'
        local NEWPATH
        local DIR
        local PATHVARIABLE=${2:-PATH}
        for DIR in ${!PATHVARIABLE} ; do
                if [ "$DIR" != "$1" ] ; then
                  NEWPATH=${NEWPATH:+$NEWPATH:}$DIR
                fi
        done
        export $PATHVARIABLE="$NEWPATH"
}

pathprepend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}"
}

pathappend () {
        pathremove $1 $2
        local PATHVARIABLE=${2:-PATH}
        export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1"
}

export -f pathremove pathprepend pathappend

Ссылка: http://www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html

person kevinarpe    schedule 22.11.2015

Я знаю, что этот вопрос касается BASH, который все должны предпочесть, но, поскольку мне нравится симметрия и иногда мне нужно использовать csh, я создал эквивалент для path_prepend (), path_append () и path_remove () "элегантное решение выше.

Суть в том, что "csh" не имеет функций, поэтому я помещаю в свой личный каталог bin небольшие сценарии оболочки, которые действуют как функции. Я создаю псевдонимы для ИСТОЧНИКА этих сценариев, чтобы внести изменения в назначенную переменную среды.

~ / bin / _path_remove.csh:

set _resolve = `eval echo $2`
setenv $1 `eval echo -n \$$1 | awk -v RS=: -v ORS=: '$1 != "'${_resolve}'"' | sed 's/:$//'`;
unset _resolve

~ / bin / _path_append.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_base}:${_resolve}
unset _base _resolve

~ / bin / _path_prepend.csh:

source ~/bin/_path_remove.csh $1 $2
set _base = `eval echo \$$1`
set _resolve = `eval echo $2`
setenv $1 ${_resolve}:${_base}
unset _base _resolve

~ / bin / .cshrc:

…
alias path_remove  "source ~/bin/_path_remove.csh  '\!:1' '\!:2'"
alias path_append  "source ~/bin/_path_append.csh  '\!:1' '\!:2'"
alias path_prepend "source ~/bin/_path_prepend.csh '\!:1' '\!:2'"
…

Вы можете использовать их вот так ...

%(csh)> path_append MODULEPATH ${HOME}/modulefiles
person Lance E.T. Compte    schedule 08.06.2018

Поскольку это имеет тенденцию быть довольно проблематичным, поскольку НЕТ элегантного способа, я рекомендую избегать проблемы, переставляя решение: создавайте свой PATH, а не пытайтесь его разрушить.

Я мог бы быть более конкретным, если бы знал реальный контекст вашей проблемы. А пока я буду использовать в качестве контекста сборку программного обеспечения.

Распространенная проблема со сборками программного обеспечения заключается в том, что они ломаются на некоторых машинах, в конечном итоге из-за того, как кто-то настроил свою оболочку по умолчанию (PATH и другие переменные среды). Элегантное решение - сделать ваши сценарии сборки невосприимчивыми, полностью указав среду оболочки. Кодируйте свои сценарии сборки, чтобы установить PATH и другие переменные среды на основе сборки частей, которые вы контролируете, таких как расположение компилятора, библиотек, инструментов, компонентов и т. Д. Сделайте каждый настраиваемый элемент чем-то, что вы можете индивидуально настраивать, проверять и затем используйте его соответствующим образом в своем сценарии.

Например, у меня есть сборка Java для WebLogic на основе Maven, которую я унаследовал от своего нового работодателя. Скрипт сборки известен своей хрупкостью, и мы с другим новым сотрудником потратили три недели (не полный рабочий день, просто кое-где, но все же много часов), заставляя его работать на наших машинах. Важным шагом было то, что я взял под свой контроль PATH, чтобы точно знать, какая Java, какая Maven и какая WebLogic вызывается. Я создал переменные среды, указывающие на каждый из этих инструментов, затем вычислил PATH на основе этих и нескольких других. Подобные методы обуздали другие настраиваемые параметры, пока мы, наконец, не создали воспроизводимую сборку.

Между прочим, не используйте Maven, Java в порядке, и покупайте WebLogic только в том случае, если вам абсолютно необходима его кластеризация (но в остальном нет, и особенно его проприетарные функции).

С наилучшими пожеланиями.

person Rob Williams    schedule 16.12.2008
comment
иногда у вас нет root-доступа, и ваш администратор управляет вашим PATH. Конечно, вы можете построить собственное, но каждый раз, когда ваш администратор перемещает что-то, вам нужно выяснить, куда он это поместил. Такого рода поражение преследует цель иметь админа. - person Shep; 24.04.2012

Как и в случае с @litb, я ответил на вопрос «Как можно ли манипулировать элементами $ PATH в сценариях оболочки ", так что мой главный ответ там.

Функциональность «разделения» в bash и других производных оболочки Борна наиболее точно достигается с помощью $IFS, разделителя полей. Например, чтобы установить позиционные аргументы ($1, $2, ...) для элементов PATH, используйте:

set -- $(IFS=":"; echo "$PATH")

Он будет работать нормально, пока в $ PATH нет пробелов. Заставить его работать для элементов пути, содержащих пробелы, - нетривиальное упражнение, оставшееся для заинтересованного читателя. Вероятно, проще справиться с этим, используя язык сценариев, такой как Perl.

У меня также есть сценарий clnpath, который я часто использую для установки моей переменной PATH. Я задокументировал это в ответе на вопрос "Как избежать дублирование переменной PATH в csh ".

person Jonathan Leffler    schedule 16.12.2008
comment
IFS =: a = ($ ПУТЬ); IFS = split тоже хорош. работает, если они тоже содержат пробелы. но затем у вас есть массив, и вам нужно возиться с циклами for и т.д., чтобы удалить имена. - person Johannes Schaub - litb; 16.12.2008
comment
Да; это становится неудобно - как и в случае с моим обновленным комментарием, на данном этапе, вероятно, проще использовать язык сценариев. - person Jonathan Leffler; 16.12.2008

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

Вот немного менее элегантная версия, которая удаляет один каталог из $PATH, используя только строковые операции. Я это проверил.

#!/bin/bash
#
#   remove_from_path dirname
#
#   removes $1 from user's $PATH

if [ $# -ne 1 ]; then
  echo "Usage: $0 pathname" 1>&2; exit 1;
fi

delendum="$1"
NEWPATH=
xxx="$IFS"
IFS=":"
for i in $PATH ; do
  IFS="$xxx"
  case "$i" in
    "$delendum") ;; # do nothing
    *) [ -z "$NEWPATH" ] && NEWPATH="$i" || NEWPATH="$NEWPATH:$i" ;;
  esac
done

PATH="$NEWPATH"
echo "$PATH"
person Norman Ramsey    schedule 16.12.2008

Вот однострочник Perl:

PATH=`perl -e '$a=shift;$_=$ENV{PATH};s#:$a(:)|^$a:|:$a$#$1#;print' /home/usr/bin`

Переменная $a получает путь, который нужно удалить. Команды s (замена) и print неявно работают с переменной $_.

person J. A. Faucett    schedule 16.12.2008

Здесь хорошие вещи. Я использую его, чтобы вообще не добавлять дубли.

#!/bin/bash
#
######################################################################################
#
# Allows a list of additions to PATH with no dupes
# 
# Patch code below into your $HOME/.bashrc file or where it
# will be seen at login.
#
# Can also be made executable and run as-is.
#
######################################################################################

# add2path=($HOME/bin .)                  ## uncomment space separated list 
if [ $add2path ]; then                    ## skip if list empty or commented out
for nodup in ${add2path[*]}
do
    case $PATH in                 ## case block thanks to MIKE511
    $nodup:* | *:$nodup:* | *:$nodup ) ;;    ## if found, do nothing
    *) PATH=$PATH:$nodup          ## else, add it to end of PATH or
    esac                          ## *) PATH=$nodup:$PATH   prepend to front
done
export PATH
fi
## debug add2path
echo
echo " PATH == $PATH"
echo
person ongoto    schedule 13.01.2010
comment
Вы можете упростить оператор case, добавив в строку PATH начальное и конечное двоеточие: case ":$PATH:" in (*:"$nodup":*) ;; (*) PATH="$PATH:$nodup" ;; esac - person glenn jackman; 23.06.2011

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

# delete all /opt/local paths in PATH
shopt -s extglob 
printf "%s\n" "${PATH}" | tr ':' '\n' | nl
printf "%s\n" "${PATH//+(\/opt\/local\/)+([^:])?(:)/}" | tr ':' '\n' | nl 

man bash | less -p extglob
person carlo    schedule 20.01.2010

Расширенная подстановка однострочников (ну вроде как):

path_remove ()  { shopt -s extglob; PATH="${PATH//+(${1})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 

Кажется, нет необходимости избегать косой черты в $ 1.

path_remove ()  { shopt -s extglob; declare escArg="${1//\//\\/}"; PATH="${PATH//+(${escArg})+([^:])?(:)/}"; export PATH="${PATH%:}"; shopt -u extglob; return 0; } 
person carlo    schedule 21.01.2010

Добавляя двоеточия в PATH, мы также можем сделать что-то вроде:

path_remove ()  { 
   declare i newPATH
   # put a colon at the beginning & end AND double each colon in-between
   newPATH=":${PATH//:/::}:"   
   for ((i=1; i<=${#@}; i++)); do
       #echo ${@:${i}:1}
       newPATH="${newPATH//:${@:${i}:1}:/}"   # s/:\/fullpath://g
   done
   newPATH="${newPATH//::/:}"
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 


path_remove_all ()  {
   declare i newPATH extglobVar
   extglobVar=0
   # enable extended globbing if necessary
   [[ ! $(shopt -q extglob) ]]  && { shopt -s extglob; extglobVar=1; }
   newPATH=":${PATH}:"
   for ((i=1; i<=${#@}; i++ )); do
      newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}"     # s/:\/path[^:]*//g
   done
   newPATH="${newPATH#:}"      # remove leading colon
   newPATH="${newPATH%:}"      # remove trailing colon
   # disable extended globbing if it was enabled in this function
   [[ $extglobVar -eq 1 ]] && shopt -u extglob
   unset PATH 
   PATH="${newPATH}" 
   export PATH
   return 0 
} 

path_remove /opt/local/bin /usr/local/bin

path_remove_all /opt/local /usr/local 
person proxxy    schedule 22.01.2010

В path_remove_all (через прокси):

-newPATH="${newPATH//:+(${@:${i}:1})*([^:])/}" 
+newPATH="${newPATH//:${@:${i}:1}*([^:])/}"        # s/:\/path[^:]*//g 
person marius    schedule 22.01.2010

Хотя это очень старый поток, я подумал, что это решение может быть интересно:

PATH="/usr/lib/ccache:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
REMOVE="ccache" # whole or part of a path :)
export PATH=$(IFS=':';p=($PATH);unset IFS;p=(${p[@]%%$REMOVE});IFS=':';echo "${p[*]}";unset IFS)
echo $PATH # outputs /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games

нашел его в этом сообщении в блоге. Думаю, этот мне нравится больше всего :)

person mjc    schedule 22.06.2011

Я использовал несколько иной подход, чем большинство людей здесь, и сосредоточился конкретно на манипуляциях со строками, например:

path_remove () {
    if [[ ":$PATH:" == *":$1:"* ]]; then
        local dirs=":$PATH:"
        dirs=${dirs/:$1:/:}
        export PATH="$(__path_clean $dirs)"
    fi
}
__path_clean () {
    local dirs=${1%?}
    echo ${dirs#?}
}

Вышеупомянутый упрощенный пример последних функций, которые я использую. Я также создал path_add_before и path_add_after, позволяющие вставлять путь до / после указанного пути уже в PATH.

Полный набор функций доступен в path_helpers.sh. в моих файлах точек. Они полностью поддерживают удаление / добавление / добавление / вставку в начале / середине / конце строки PATH.

person jimeh    schedule 06.02.2012

Завершающий ":" вызван тем, что вы устанавливаете конец строки, а не разделитель. Я использую блоки с ограниченными ресурсами и люблю собирать все в один скрипт без этих странностей:

path_remove () {
    PATH="$(echo -n $PATH | awk -v RS=: -v ORS= '$0 != "'$1'"{print s _ $0;s=":"}')"
}
person Coroos    schedule 16.05.2014

Это, конечно, элегантно, но в нем используется внешний sed. Кроме того, он удаляет все пути, содержащие строку поиска $ 1. Он также не оставляет болтающихся: в конце, если удаленный путь является последним в PATH.

PATH=`echo $PATH | sed 's/:[^:]*$1[^:]*//g'`

Эта альтернатива действительно оставляет висящий финал: однако.

PATH=`echo $PATH | tr ":" "\n" | grep -v $1 | tr "\n" ":"`

Альтернативы без обратных кавычек:

PATH=$(echo $PATH | sed 's/:[^:]*$1[^:]*//g')

PATH=$(echo $PATH | tr ":" "\n" | grep -v $1 | tr "\n" ":")
person Eugene    schedule 22.10.2014

Давайте «удалим» ссылки на / path / to / something из PATH:

Баш:

PATH=${PATH/something/nope/}

Бонусный ответ для Windows Batch:

set PATH=%PATH:something=nope%

У кого-нибудь есть решение попроще? :)

person spike83    schedule 28.04.2019