从 Bash 中的 $PATH 变量中删除路径的最优雅方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/370047/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:51:23  来源:igfitidea点击:

What is the most elegant way to remove a path from the $PATH variable in Bash?

bashshellpathvariable-expansionlist-processing

提问by Ben Blank

Or more generally, how do I remove an item from a colon-separated list in a Bash environment variable?

或者更一般地说,如何从 Bash 环境变量中以冒号分隔的列表中删除项目?

I thought I had seen a simple way to do this years ago, using the more advanced forms of Bash variable expansion, but if so I've lost track of it. A quick search of Google turned up surprisingly few relevant results and none that I would call "simple" or "elegant". For example, two methods using sed and awk, respectively:

几年前我以为我已经看到了一种简单的方法来做到这一点,使用更高级的 Bash 变量扩展形式,但如果是这样,我已经忘记了它。谷歌的快速搜索令人惊讶地发现很少有相关结果,而且没有一个我会称之为“简单”或“优雅”的。比如分别使用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)

Does nothing straightforward exist? Is there anything analogous to a split() function in Bash?

没有什么直截了当的存在吗?有没有类似于 Bash 中的 split() 函数的东西?

Update:
It looks like I need to apologize for my intentionally-vague question; I was less interested in solving a specific use-case than in provoking good discussion. Fortunately, I got it!

更新:
看来我需要为我故意含糊的问题道歉;我对解决特定用例的兴趣不大,而是对引发良好的讨论感兴趣。幸运的是,我得到了!

There are some very clever techniques here. In the end, I've added the following three functions to my toolbox. The magic happens in path_remove, which is based largely on Martin York's clever use of awk's RS variable.

这里有一些非常聪明的技巧。最后,我在我的工具箱中添加了以下三个函数。神奇发生在 path_remove 中,它主要基于 Martin York 对awkRS 变量的巧妙使用。

path_append ()  { path_remove ; export PATH="$PATH:"; }
path_prepend () { path_remove ; export PATH=":$PATH"; }
path_remove ()  { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '
# Strip all paths with SDE in them.
#
export PATH=`echo ${PATH} | awk -v RS=: -v ORS=: '/SDE/ {next} {print}'`
!= "''"' | sed 's/:$//'`; }

The only real cruft in there is the use of sedto remove the trailing colon. Considering how straightforward the rest of Martin's solution is, though, I'm quite willing to live with it!

唯一真正的 cruft 是使用sed删除尾随的冒号。不过,考虑到 Martin 解决方案的其余部分是多么简单,我非常愿意接受它!



Related question: How do I manipulate $PATH elements in shell scripts?

相关问题: 如何在 shell 脚本中操作 $PATH 元素?

采纳答案by Martin York

A minute with awk:

awk 一分钟:

$ 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!

Edit: It response to comments below:

编辑:它回应以下评论:

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

Edit in response to security problem: (that is not relevant to the question)

针对安全问题进行编辑:(与问题无关)

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)

This removes any trailing colons left by deleting the last entries, which would effectively add .to your path.

这会删除通过删除最后一个条目而留下的任何尾随冒号,这将有效地添加.到您的路径中。

回答by Martin York

My dirty hack:

我的肮脏黑客:

# 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

回答by Andrew Aylett

Since the big issue with substitution is the end cases, how about making the end cases no different to the other cases? If the path already had colons at the start and end, we could simply search for our desired string wrapped with colons. As it is, we can easily add those colons and remove them afterwards.

既然替换的大问题是最终案例,那么如何使最终案例与其他案例没有区别?如果路径的开头和结尾已经有冒号,我们可以简单地搜索我们想要的用冒号包裹的字符串。事实上,我们可以轻松地添加这些冒号,然后删除它们。

#!/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[*]}"

Pure bash :).

纯粹的 bash :)。

回答by nicerobot

Here's the simplest solution i can devise:

这是我可以设计的最简单的解决方案:

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[*]}");

The above example will remove any element in $PATH that contains "usr". You can replace "*usr*" with "/home/user/bin" to remove just that element.

上面的示例将删除 $PATH 中包含“usr”的任何元素。您可以将“*usr*”替换为“/home/user/bin”以仅删除该元素。

updateper sschuberth

sschuberth更新

Even though i think spaces in a $PATHare a horribleidea, here's a solution that handles it:

尽管我认为 a$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

或者

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

回答by sschuberth

Here's a one-liner that, despite the current acceptedand highest ratedanswers, does not add invisible characters to PATH and can cope with paths that contain spaces:

这是一个单行,尽管目前接受评价最高的答案,但不会向 PATH 添加不可见字符,并且可以处理包含空格的路径:

removeFromPath() {
   local p d
   p="::"
   d=":$PATH:"
   d=${d//$p/:}
   d=${d/#:/}
   PATH=${d/%:/}
}

Personally, I also find this easy to read / understand, and it only involves common commands instead of using awk.

就我个人而言,我也觉得这很容易阅读/理解,而且它只涉及常用命令而不是使用 awk。

回答by robinbb

Here is a solution that:

这是一个解决方案:

  • is pure Bash,
  • does not invoke other processes (like 'sed' or 'awk'),
  • does not change IFS,
  • does not fork a sub-shell,
  • handles paths with spaces, and
  • removes all occurrences of the argument in PATH.

    removeFromPath() {
       local p d
       p="::"
       d=":$PATH:"
       d=${d//$p/:}
       d=${d/#:/}
       PATH=${d/%:/}
    }
  • 是纯 Bash,
  • 不调用其他进程(如“sed”或“awk”),
  • 不改变IFS
  • 不分叉子外壳,
  • 处理带有空格的路径,以及
  • 删除 中所有出现的参数PATH

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

回答by Mark Booth

The best pure bash option I have found so far is the following:

迄今为止我发现的最好的纯 bash 选项如下:

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

This is based on the not quite correct answerto Add directory to $PATH if it's not already thereover on Superuser.

这是基于将目录添加到 $PATH 的不太正确的答案如果超级用户上还没有它

回答by GreenFox

function __path_remove(){
local D=":${PATH}:";
[ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}";
PATH="${PATH/#:/}";
export PATH="${PATH/%:/}";
}

function __path_remove(){
local D=":${PATH}:";
[ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}";
PATH="${PATH/#:/}";
导出路径="${PATH/%:/}";
}

Dug it out from my .bashrc file. When you play around with PATH, and it gets lost, awk/sed/grep becomes unavailable :-)

从我的 .bashrc 文件中挖掘出来。当您使用 PATH 并且它丢失时,awk/sed/grep 变得不可用:-)

回答by Mr. Wacky

I've just been using the functions in the bash distribution, that have been there apparently since 1991. These are still in the bash-docs package on Fedora, and used to be used in /etc/profile, but no more...

我一直在使用 bash 发行版中的函数,这些函数显然自 1991 年以来一直存在。这些函数仍在 Fedora 上的 bash-docs 包中,并且曾经在/etc/profile.

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

回答by Johannes Schaub - litb

I did write an answer to this here(using awk too). But i'm not sure that's what you are looking for? It at least looks clear to me what it does, instead of trying to fit into one line. For a simple one liner, though, that only removes stuff, i recommend

我确实在这里写了一个答案(也使用 awk)。但我不确定这就是你要找的?它至少对我来说很清楚它的作用,而不是试图融入一条线。不过,对于一个简单的单衬里,它只会去除东西,我建议

##代码##

Replacing is

更换是

##代码##

or (shorter but less readable)

或(较短但可读性较差)

##代码##

Anyway, for the same question, and a whole lot of useful answers, see here.

无论如何,对于相同的问题以及大量有用的答案,请参见此处