Какой самый элегантный способ удалить путь из переменной $ PATH в Bash? - PullRequest
99 голосов
/ 16 декабря 2008

Или, в более общем смысле, как удалить элемент из списка, разделенного двоеточиями, в переменной среды 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, в основе которого лежит умное использование Мартином Йорком переменной awk RS.

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 в скриптах оболочки?

Ответы [ 34 ]

52 голосов
/ 16 декабря 2008

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

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
42 голосов
/ 16 декабря 2008

минута с 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/:*$//')

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

40 голосов
/ 21 января 2010

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

# 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

Чистый bash:).

26 голосов
/ 16 декабря 2008

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

#!/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", чтобы удалить только этот элемент.

обновление за сшуберт

Хотя я думаю, что пробелы в $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[*]}");

или

IFS=':'
t=($PATH)
n=${#t[*]}
a=()
for ((i=0;i<n;i++)); do
  p="${t[i]%%*usr*}"
  [ "${p}" ] && a[i]="${p}"
done
echo "${a[*]}"
11 голосов
/ 30 октября 2012

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

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

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

8 голосов
/ 20 марта 2015

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

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

    removeFromPath() {
       local p d
       p=":$1:"
       d=":$PATH:"
       d=${d//$p/:}
       d=${d/#:/}
       PATH=${d/%:/}
    }
6 голосов
/ 13 августа 2012

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

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

6 голосов
/ 25 октября 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.

5 голосов
/ 09 марта 2012

Я только что использовал функции в дистрибутиве 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" <sjg@zen.void.oz.au>
#Message-Id: <199510091130.VAA01188@zen.void.oz.au>
#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 <sjg@zen.void.oz.au>

#       @(#)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;:\$;;"`
}
4 голосов
/ 16 декабря 2008

Я написал ответ на этот здесь (тоже с использованием 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:

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

...