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

Ben*_*ank 106 bash shell path variable-expansion list-processing

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

我想我多年前已经看到了一种简单的方法,使用更高级的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)
Run Code Online (Sandbox Code Playgroud)

什么都不直接存在?有什么类似于Bash中的split()函数吗?

更新:
看起来我需要为我的故意模糊的问题道歉; 我对解决特定用例的兴趣不如激发良好的讨论.幸运的是,我明白了!

这里有一些非常聪明的技巧.最后,我在工具箱中添加了以下三个功能.魔法发生在path_remove中,这主要基于Martin York巧妙使用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/:$//'`; }
Run Code Online (Sandbox Code Playgroud)

唯一真正的缺点是使用sed去除尾部结肠.考虑到马丁的其他解决方案是多么简单,但我非常愿意接受它!


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

Mar*_*ork 51

我的肮脏黑客:

echo ${PATH} > t1
vi t1
export PATH=$(cat t1)
Run Code Online (Sandbox Code Playgroud)

  • 当最明显的解决方案是*de*-automate这个过程时,这绝不是一个好兆头.:-D (17认同)
  • 比“优雅”的替代品安全得多。 (2认同)

Mar*_*ork 47

与awk一分钟:

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

编辑:它回复以下评论:

$ 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!
Run Code Online (Sandbox Code Playgroud)

编辑以响应安全问题:(与问题无关)

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

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

  • 作为一个特殊规则,作为`PATH`变量*的"成员"的空字符串*表示所有Unix shell中的当前目录,至少从1979年的V7 Unix开始.它仍然在`bash`中.查看手册或亲自尝试. (2认同)
  • @Martin:POSIX不要求这种行为,但确实记录并允许它:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html (2认同)
  • 删除最后一个元素时存在一个更微妙的问题:[awk 似乎在字符串末尾添加了一个空字节](https://github.com/msysgit/msysgit/issues/67)。这意味着如果稍后将另一个目录附加到 PATH,实际上不会搜索它。 (2认同)

And*_*ett 40

由于替换的最大问题是最终案例,如何使最终案例与其他案例没有什么不同?如果路径在开始和结束时已经有冒号,我们可以简单地搜索用冒号包裹的所需字符串.事实上,我们可以轻松添加这些冒号并在之后删除它们.

# 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
Run Code Online (Sandbox Code Playgroud)

纯粹的打击:).

  • 我将这个教程部分添加到一些额外的结霜中:http://tldp.org/LDP/abs/html/string-manipulation.html (2认同)
  • 绝对是一个小宝石。当我发现这篇文章时,这正是我想要做的。-谢谢你安德鲁!顺便说一句:也许您想在“:$PATH:”周围添加双引号,以防它应该包含空格(与“/usr/bin”相同)和最后一行“$WORK”。 (2认同)
  • 谢谢@ PacMan-- :)。我很确定(只是尝试了一下)您不需要空间来分配给WORK和PATH,因为在将行解析为变量分配和命令执行的部分后,变量扩展就会发生。“ REMOVE”可能需要加引号,或者如果它是常量,则可以直接将其放入替换中。 (2认同)

nic*_*bot 26

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

#!/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[*]}"
Run Code Online (Sandbox Code Playgroud)

上面的示例将删除$ 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[*]}");
Run Code Online (Sandbox Code Playgroud)

要么

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

  • 作为一个班轮:PATH = $(IFS =':'; t =($ PATH);未设置IFS; t =($ {t [@] %%*usr*}); IFS =':'; echo"$ {T [*]}"); (2认同)

ssc*_*rth 11

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

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

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

  • ...如果你想要一些甚至可以应对文件名换行的东西,你可以使用它:`export PATH = $(p = $(echo $ PATH | tr":""\ 0"| grep -v -z "/ cygwin /"| tr"\ 0"":"); echo $ {p%:})`(尽管可以说,你可能想问问自己为什么需要这个,如果你这样做:)) (2认同)
  • @ehdr:您需要将 `echo "..."` 替换为 `printf "%s" "..."` 才能在 `-e` 等路径上工作。请参阅/sf/answers/3459288451/ (2认同)

小智 8

这是一个解决方案:

  • 是纯粹的Bash,
  • 不会调用其他进程(如'sed'或'awk'),
  • 不改变IFS,
  • 不分叉子壳,
  • 处理带空格的路径,和
  • 删除所有出现的参数PATH.

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

  • 我喜欢这个解决方案.也许让变量名更具描述性? (4认同)
  • 很不错。但是,不适用于包含星号 (*) 的路径段。有时他们会无意中到达那里。 (2认同)

Gre*_*Fox 6

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

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

  • 这是一个很好的观点。(我从来不喜欢为像这样的简单事情执行外部实用程序)。 (2认同)

Mar*_*oth 6

到目前为止我发现的最好的纯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
}
Run Code Online (Sandbox Code Playgroud)

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

  • 当“$PATH”包含目标(即要删除的)路径的子文件夹时,此解决方案将失败。例如:“a:abc/def/bin:b”-&gt;“a/bin:b”,当要删除“abc/def”时。 (2认同)

小智 5

我一直在使用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;:\$;;"`
}
Run Code Online (Sandbox Code Playgroud)


kev*_*rpe 5

Linux from Scratch 在 中定义了三个 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
Run Code Online (Sandbox Code Playgroud)

参考: http: //www.linuxfromscratch.org/blfs/view/svn/postlfs/profile.html