在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))")"
(而不是从echo
pipe道, 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`
$a
variables获取要删除的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_before
和path_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" ":")