在 Bash 中从 $PATH 变量中删除路径的最优雅的方法是什么?
-
21-08-2019 - |
题
或者更一般地说,如何从 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)
难道没有什么直接的事情存在吗?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/:$//'`; }
唯一真正的问题是使用 sed
删除尾随的冒号。不过,考虑到马丁解决方案的其余部分是多么简单,我非常愿意接受它!
解决方案
与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 => /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下)
下面是我能设计出最简单的解决方案:
#!/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[*]}"
在上面的例子将删除在包含“USR” $ PATH的任何元件。您可以替换 “* 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[*]}");
或
IFS=':'
t=($PATH)
n=${#t[*]}
a=()
for ((i=0;i<n;i++)); do
p="${t[i]%%*usr*}"
[ "${p}" ] && a[i]="${p}"
done
echo "${a[*]}"
这是一个解决方案:
- 是纯Bash,
- 不调用其他进程(如“sed”或“awk”),
- 没有改变
IFS
, - 不分叉子外壳,
- 处理带有空格的路径,并且
删除所有出现的参数
PATH
.removeFromPath() { local p d p=":$1:" d=":$PATH:" d=${d//$p/:} d=${d/#:/} PATH=${d/%:/} }
功能__path_remove(){结果 当地d = “$ {PATH}:” 结果 [ “$ {d /:$ 1:/:}”= “$ d”!] && PATH = “$ {d /:$ 1:/:}”;结果, PATH = “$ {PATH /#:/}”;点击 出口PATH = “$ {PATH /%:/}”;点击 }
这是我的.bashrc文件挖了出来。 当你玩的,它确实丢失时,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(如果尚不存在) 关于超级用户。
我刚刚被使用在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;:\$;;"`
}
我确实写了一个答案 这里 (也使用 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:
无论如何,对于同样的问题和很多有用的答案,请参阅 这里.
那么,在bash,因为它支持的正则表达式,我会简单地做:
PATH=${PATH/:\/home\/user\/bin/}
什么是最优雅的方式来消除猛砸从$ PATH变量的路径?
有什么比AWK更优雅?
path_remove () { export PATH=`echo -n $PATH | awk -v RS=: -v ORS=: '$0 != "'$1'"' | sed 's/:$//'`;
的Python!这是一个更好的可读性和维护的解决方案,而且很容易检查,以看到它真正做你想要什么。
请说出您想删除的第一路径元件?
PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[0]; print(':'.join(path))")"
(而不是从echo
管道,os.getenv['PATH']
会有点短,并提供了相同的结果上面,但我担心的Python可能会做与环境变量的东西,所以它可能是最好的管道直接从环境你关心。)
类似地从端删除:
PATH="$(echo "$PATH" | python -c "import sys; path = sys.stdin.read().split(':'); del path[-1]; print(':'.join(path))")"
为了使这些可重复使用的外壳功能,你可以,例如,粘在你的.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))")"
}
是,把一个在结肠路径的端部,例如,使得除去的路径少一点笨拙&容易出错。
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
$文件夹可以被任何东西所取代,并且可以包含空格( “/ home / user中/我的文档”)
在最优雅纯的bash溶液我发现日期:
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"
}
其他大多数建议的解决方案的只依靠字符串匹配并没有考虑到含有像.
,..
,或~
特殊的名字帐户路径段。下面的bash函数解析目录字符串在其参数和在路径段寻找逻辑目录相匹配以及字符串匹配。
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}
}
测试:
$ 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'
Linux的从头/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
价: http://www.linuxfromscratch.org/blfs/视图/ SVN / postlfs / profile.html
我喜欢@ BenBlank的更新显示出他原来的问题有三种功能。为了概括它们,我使用了2参数的形式,它允许我设置PATH或任何其他环境变量欲:
path_append () { path_remove $1 $2; export $1="${!1}:$2"; }
path_prepend () { path_remove $1 $2; export $1="$2:${!1}"; }
path_remove () { export $1="`echo -n ${!1} | awk -v RS=: -v ORS=: '$1 != "'$2'"' | sed 's/:$//'`"; }
使用的例子:
path_prepend PATH /usr/local/bin
path_append PERL5LIB "$DEVELOPMENT_HOME/p5/src/perlmods"
请注意,我还添加了一些引号,以允许包含空格的路径名的适当的处理。
由于这往往是相当有问题的,因为在没有优雅的方式,我建议通过重新排列的解决方案,避免该问题:建立你的PATH起来,而不是试图将其摧毁
我能更具体,如果我知道你真正的问题情境。在此期间,我将使用一个软件构造作为上下文
软件的一个常见问题是建立,它打破某些机器上,最终因某人是如何配置他们默认的shell(PATH和其他环境变量)。优雅的解决方案是构建脚本的免疫通过完全指定shell环境。代码生成脚本中设置基于组装你控制部件,如编译器,库,工具组件的位置等使每个配置项的东西,你可以单独设置,验证路径和其他环境变量,然后在脚本中使用适当
例如,我有我继承了我的新雇主基于Maven的WebLogic的有针对性的Java构建。构建脚本是臭名昭著的是脆弱的,而另一个新的员工和我花了三个星期(不是全职,只是在这里和那里,但仍有许多小时)得到它在我们的机器工作。一个重要的步骤是,我从小路的控制,使我清楚地知道这其中的Maven,并在被调用的Java中,WebLogic。我创建环境变量指向每个工具的话,我计算出根据这些以及一些其他的路径。类似的技术驯服了其他配置的设置,直到我们终于创造了一个可再生的构建。
顺便说一句,不使用Maven,Java是没关系的,只有在绝对有必要的集群买的WebLogic(但是否则没有,尤其是不是它的专有功能)。
最佳的愿望。
与@litb一样,我贡献了这个问题的答案“如何在 shell 脚本中操作 $PATH 元素”,所以我的主要答案就在那里。
中的“拆分”功能 bash
和其他 Bourne shell 衍生物是最巧妙地实现的 $IFS
, ,字段间分隔符。例如,要设置位置参数 ($1
, $2
, ...) 到 PATH 的元素,使用:
set -- $(IFS=":"; echo "$PATH")
只要 $PATH 中没有空格,它就可以正常工作。使其适用于包含空格的路径元素是一项不简单的练习 - 留给感兴趣的读者。使用 Perl 等脚本语言来处理它可能更简单。
我也有一个剧本 clnpath
, ,我广泛使用它来设置我的路径。我在“的答案中记录了它如何避免在 csh 中重复 PATH 变量".
是什么让这个恼人的问题是第一和最后一个元素之间的栅栏柱的情况。该问题可以通过改变IFS和使用阵列来优雅的解决,但我不知道如何重新引入结肠一旦路径被转换为阵列形式。
下面是一个略小于优雅版去除仅使用字符串操作从$PATH
一个目录。我已经测试它。
#!/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
变量获得要删除的路径。所述s
(替代物)和print
命令隐含在$_
变量操作。
在这里好东西。 我用这一个从在第一位置加入愚弄保持。
#!/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
扩展通配的一行(井,排序的):
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
虽然这是一个非常古老的线程,我想这个解决方案可能会感兴趣的:
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
发现它这个博客文章。我想,我喜欢这个最:)
我花了比一般人稍微不同的方法在这里和刚刚字符串操作具体关注的焦点,像这样:
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#?}
}
以上是我使用最终功能的简化示例。我还创建了path_add_before
和path_add_after
让您之前插入的路径/ PATH中已经指定的路径后。
在全套功能在 path_helpers.sh <可用/ A>在我的点文件。它们完全支持删除/添加/预谋/在PATH串的开头/中间/结束插入。
在末尾的“:”由一个事实,即要设置的行结束,而不是分离器引起的。我使用的资源limitted单位和喜欢的一切打包到一个脚本,如果没有这些怪事:
path_remove () {
PATH="$(echo -n $PATH | awk -v RS=: -v ORS= '$0 != "'$1'"{print s _ $0;s=":"}')"
}