在Bash中从$ PATHvariables中删除path的最优雅的方法是什么?

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

我以为我曾经见过一个很简单的方法,使用更高级的Bashvariables扩展forms,但是如果是这样,我已经失去了它的踪迹。 谷歌的快速search出乎意料地less了相关的结果,没有,我会称之为“简单”或“优雅”。 例如,两个分别使用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) 

没有直接的存在吗? 在Bash中有没有类似split()的函数?

更新:
看来我要为我故意模糊的问题道歉, 我对解决一个具体的使用案例不感兴趣,而不是激起良好的讨论。 幸运的是,我明白了!

这里有一些非常聪明的技巧。 最后,我将以下三个函数添加到了我的工具箱中。 这个神奇的发生在path_remove中,主要是基于Martin York聪明地使用awk的RSvariables。

 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去除尾随冒号。 考虑到马丁解决scheme的其余部分是多么的简单,我非常愿意接受它!


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

一分钟用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/:*$//') 

这删除了删除最后的条目,这将有效地添加留下的任何结尾的冒号. 到你的路上。

我的肮脏的黑客:

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

由于替代的大问题就是最终的情况,那么如何使终结案与其他案件没有什么不同呢? 如果path在开始和结束时已经有冒号,我们可以简单地search用冒号包裹的所需string。 事实上,我们可以很容易地添加这些冒号,然后将其删除。

 # 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 :)。

这是我可以devise的最简单的解决scheme:

 #!/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 *”replace成“/ home / user / bin”来删除那个元素。

更新每个sschuberth

即使我认为$PATH中的空格是一个可怕的想法,下面是一个处理它的解决scheme:

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

尽pipe目前已被接受且评分最高的答案,这里是一个单线程,不会为PATH添加无形的字符,并且可以处理包含空间的path:

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

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

函数__path_remove(){
本地D =“:$ {PATH}:”;
[“$ {D /:$ 1:/:}”!=“$ D”] && PATH =“$ {D /:$ 1:/:}”;
PATH = “$ {PATH /#:/}”;
export PATH =“$ {PATH /%:/}”;
}

从我的.bashrc文件中挖出来。 当你玩弄PATH,它会迷路,awk / sed / grep变得不可用:-)

到目前为止,我发现的最好的纯粹的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,如果它尚未在超级用户上。

这是一个解决scheme:

  • 是纯粹的Bash,
  • 不会调用其他进程(如'sed'或'awk'),
  • 不会改变IFS
  • 不分叉一个子shell,
  • 用空格处理path,
  • 删除PATH中所有出现的参数。

     removeFromPath(){
       本地人
        P = “:$ 1:”
        d = “:$ PATH”
        d = $ {d // $ P /:}
        d = $ {d /#:/}
        PATH = $ {d /%:/}
     } 

我刚刚使用了bash发行版中的函数,这些函数自1991年以来一直存在。这些函数仍然位于Fedora的bash-docs包中,并且已经用于/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;:\$;;"` } 

那么,在bash中,因为它支持正则expression式,所以我简单地做:

 PATH=${PATH/:\/home\/user\/bin/} 

我在这里写了一个答案(也使用awk)。 但我不确定那是你在找什么? 它至less在我看来清楚它的作用,而不是试图融入一条线。 对于一个简单的class轮,但是,这只是删除的东西,我build议

 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: 

无论如何,对于同样的问题,以及大量有用的答案,请看这里 。

是的,例如,在PATH的末尾添加一个冒号,使得删除一个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 

如果您担心删除$ PATH中的重复项 ,恕我直言,最优雅的方式将不会将它们添加到首位。 在1行中:

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

$文件夹可以被任何东西replace,并且可以包含空格(“/ home / user / my documents”)

迄今为止我发现的最优雅的纯bash解决scheme:

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

大多数其他build议的解决scheme只依赖于string匹配,并不考虑包含特殊名称的path段...~ 。 下面的bash函数parsing了参数和path段中的目录string,以查找逻辑目录匹配以及string匹配。

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

testing:

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

在Bash中从$ PATHvariables中删除path的最优雅的方法是什么?

什么比awk更优雅?

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

python! 这是一个更具可读性和可维护性的解决scheme,很容易检查是否真的在做你想做的事情。

假设你想删除第一个path元素?

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

(而不是从echopipe道, os.getenv['PATH']会稍微短一些,并提供与上述相同的结果,但我担心Python可能会做一些与该环境variables,所以这可能是最好的直接从你关心的环境pipe道。)

同样从结尾删除:

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

例如,要使这些可重用的shell函数可以保存在.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))")" } 

因为这往往是相当有问题的,因为在那里没有优雅的方式,我build议通过重新排列解决scheme来避免这个问题:build立你的PATH,而不是试图把它撕下来。

如果我知道你真正的问题背景,我可以更具体一些。 在此期间,我将使用软件构build作为上下文。

软件构build的一个常见问题是它在某些机器上发生故障,最终是由于某人configuration了其默认shell(PATH和其他环境variables)。 优雅的解决scheme是通过完全指定shell环境来使您的构build脚本免于使用。 对构build脚本进行编码,以便根据您所控制的组件,例如编译器,库,工具,组件等的位置来设置PATH和其他环境variables。使每个可configuration项目可以单独设置,validation,然后在您的脚本中适当地使用。

例如,我有一个基于Maven的,基于WebLogic的Java构build,这是我在新雇主inheritance的。 构build脚本是脆弱的臭名昭着的,另一个新员工和我花了三个星期(不是全职,只是在这里,那里,但仍然很多小时)让我们的机器上工作。 一个关键的步骤是我控制了PATH,以便我确切地知道哪个Java,哪个Maven以及哪个WebLogic被调用。 我创build了环境variables来指向这些工具中的每一个,然后根据这些工具和其他一些工具计算出PATH。 类似的技术驯服其他可configuration的设置,直到我们最终创build了一个可重复的构build。

顺便说一下,不要使用Maven,Java是好的,只有在你需要集群的时候才能购买WebLogic(但是否定的,特别是它的专有特性)。

最好的祝愿。

与@litb一样,我对“ 如何在shell脚本中操作$ PATH元素 ”提出了一个答案,所以我的主要答案就在那里。

bash和其他Bourne shell衍生产品中的“拆分”function最为巧妙的是使用$IFS (字段间分隔符)。 例如,要将位置参数( $1$2 ,…)设置为PATH的元素,请使用:

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

只要$ PATH中没有空格就可以工作。 使它适用于包含空格的path元素是一个不重要的练习 – 留给感兴趣的读者。 使用诸如Perl之类的脚本语言来处理它可能更简单。

我也有一个脚本, clnpath ,我广泛用于设置我的path。 我在“ 如何防止在csh中复制PATHvariables ”的答案中logging它。

什么使这个问题恼人的是第一个和最后一个要素之间的fencepost情况。 这个问题可以通过更改IFS和使用数组来优雅地解决,但是我不知道如何在path转换为数组forms时重新引入冒号。

这是一个稍微不太优雅的版本,它只使用string操作从$PATH中删除一个目录。 我已经testing过了。

 #!/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" 

这是一个Perl单线程:

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

$avariables获取要删除的path。 s (替代)和print命令隐式地在$_variables上运行。

这里好东西 我使用这个来防止首先添加模糊。

 #!/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 

通过启用扩展通配符,可以执行以下操作:

 # 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 

扩展globbing单行(好,有点):

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

添加冒号到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 

在path_remove_all(通过proxxy):

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

虽然这是一个非常古老的线程,但我认为这个解决scheme可能是有趣的:

 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 

发现它在这个博客文章 。 我觉得我最喜欢这个:)

我采取了与这里的大多数人略有不同的方法,专注于string操作,就像这样:

 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#?} } 

以上是我使用的最终function的一个简单例子。 我还创build了path_add_beforepath_add_after允许您在PATH中的指定path之前/之后插入path。

完整的函数集在我的点文件中的path_helpers.sh中可用 。 他们完全支持PATHstring的开始/结束/删除/附加/预先/插入。

尾随的':'是由于你设置行结束,而不是分隔符。 我使用资源限制的单位,喜欢把所有东西都打包成一个脚本,没有这些怪事:

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

这当然是优雅的,但它确实使用外部sed。 此外,它删除包含searchstring$ 1的所有path。 它也不会留下一个悬而未决的:最后,如果删除的path是PATH中的最后一个。

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

然而这个select确实留下了一个悬而未决的决定。

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

没有反引号的select是:

 PATH=$(echo $PATH | sed 's/:[^:]*$1[^:]*//g') PATH=$(echo $PATH | tr ":" "\n" | grep -v $1 | tr "\n" ":") 

这当然是优雅的,但它确实使用外部sed。 此外,它删除包含searchstring$1所有path。 它们也没有留下任何悬而未决的问题:最后,如果删除的path是PATH中的最后一个。

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

然而这个select确实留下了一个悬而未决的决定。

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

PS:我不知道如何让代码中显示我的back-ticks。 所以,替代scheme是:

 PATH=$(echo $PATH | sed 's/:[^:]*$1[^:]*//g') PATH=$(echo $PATH | tr ":" "\n" | grep -v $1 | tr "\n" ":")