32

有没有一种惯用的方法从类似 PATH 的 shell 变量中删除元素?

那就是我想拿

PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/bin:/path/to/app/bin:.

删除替换不破坏变量的/path/to/app/bin其余部分。允许我新元素放在任意位置的额外积分。目标可以通过明确定义的字符串来识别,并且可以出现在列表中的任何位置。

我知道我已经看到了这一点,并且可能可以自己拼凑一些东西,但我正在寻找一种好的方法。便携性和标准化是一个优势。

我使用 bash,但也欢迎在您最喜欢的 shell 中使用示例。


这里的上下文是需要在一个大型科学分析包的多个版本(一个用于分析,另一个用于处理框架)之间方便地切换,该包产生几十个可执行文件,数据存储在文件系统周围,并使用环境变量帮助找到所有这些东西。我想编写一个选择版本的脚本,并且需要能够删除$PATH与当前活动版本相关的元素并将它们替换为与新版本相关的相同元素。


$PATH这与重新运行登录脚本等时防止重复元素的问题有关。


4

12 回答 12

20

解决 dmckee 提出的解决方案:

  1. 虽然某些版本的 Bash 可能允许在函数名称中使用连字符,但其他版本 (MacOS X) 则不允许。
  2. 我认为不需要在函数结束之前立即使用 return。
  3. 我认为不需要所有分号。
  4. 我不明白为什么您要逐个路径元素导出一个值。将export其视为等效于设置(甚至创建)全局变量 - 尽可能避免的事情。
  5. 我不确定您希望 ' replace-path PATH $PATH /usr' 做什么,但它并没有达到我的预期。

考虑一个开始包含的 PATH 值:

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

我得到的结果(来自' replace-path PATH $PATH /usr')是:

.
/Users/jleffler/bin
/local/postgresql/bin
/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/local/bin
/bin
/bin
/sw/bin
/sbin
/sbin

我本来希望得到我的原始路径,因为 /usr 没有作为(完整的)路径元素出现,而只是作为路径元素的一部分。

这可以replace-path通过修改以下sed命令之一来解决:

export $path=$(echo -n $list | tr ":" "\n" | sed "s:^$removestr\$:$replacestr:" |
               tr "\n" ":" | sed "s|::|:|g")

我用':'而不是'|' 分隔替换的部分,因为 '|' 可以(理论上)出现在路径组件中,而根据 PATH 的定义,冒号不能。我观察到第二个sed可以从 PATH 中间消除当前目录。也就是说,PATH 的合法(尽管不正常)值可能是:

PATH=/bin::/usr/local/bin

处理后,当前目录将不再位于 PATH 上。

锚定匹配的类似更改适用于path-element-by-pattern

export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 "^$pat\$")

我顺便注意到这grep -m 1不是标准的(它是一个 GNU 扩展,在 MacOS X 上也可用)。而且,确实,-n选项echo也是非标准的;您最好简单地删除通过将换行符从 echo 转换为冒号而添加的尾随冒号。由于 path-element-by-pattern 只使用一次,具有不良副作用(它破坏了任何预先存在的名为 的导出变量$removestr),它可以被它的主体合理地替换。这与更自由地使用引号以避免空格或不需要的文件名扩展问题一起导致:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace_path         PATH $PATH /exact/path/to/remove
#    replace_path_pattern PATH $PATH <grep pattern for target path>
#
# To replace a path:
#    replace_path         PATH $PATH /exact/path/to/remove /replacement/path
#    replace_path_pattern PATH $PATH <target pattern> /replacement/path
#
###############################################################################

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a ":" delimited list to work from (e.g. $PATH)
#   $3 the precise string to be removed/replaced
#   $4 the replacement string (use "" for removal)
function replace_path () {
    path=$1
    list=$2
    remove=$3
    replace=$4        # Allowed to be empty or unset

    export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
                   tr "\n" ":" | sed 's|:$||')
}

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a ":" delimited list to work from (e.g. $PATH)
#   $3 a grep pattern identifying the element to be removed/replaced
#   $4 the replacement string (use "" for removal)
function replace_path_pattern () {
    path=$1
    list=$2
    removepat=$3
    replacestr=$4        # Allowed to be empty or unset

    removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
    replace_path "$path" "$list" "$removestr" "$replacestr"
}

我有一个 Perl 脚本echopath,在调试类似 PATH 的变量的问题时我发现它很有用:

#!/usr/bin/perl -w
#
#   "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $"
#
#   Print the components of a PATH variable one per line.
#   If there are no colons in the arguments, assume that they are
#   the names of environment variables.

@ARGV = $ENV{PATH} unless @ARGV;

foreach $arg (@ARGV)
{
    $var = $arg;
    $var = $ENV{$arg} if $arg =~ /^[A-Za-z_][A-Za-z_0-9]*$/;
    $var = $arg unless $var;
    @lst = split /:/, $var;
    foreach $val (@lst)
    {
            print "$val\n";
    }
}

当我在下面的测试代码上运行修改后的解决方案时:

echo
xpath=$PATH
replace_path xpath $xpath /usr
echopath $xpath

echo
xpath=$PATH
replace_path_pattern xpath $xpath /usr/bin /work/bin
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath $xpath "/usr/.*/bin" /work/bin
echopath xpath

输出是:

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

.
/Users/jleffler/bin
/usr/local/postgresql/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/work/bin
/bin
/sw/bin
/usr/sbin
/sbin

.
/Users/jleffler/bin
/work/bin
/usr/local/mysql/bin
/Users/jleffler/perl/v5.10.0/bin
/usr/local/bin
/usr/bin
/bin
/sw/bin
/usr/sbin
/sbin

这对我来说是正确的——至少,对于我对问题所在的定义而言。

我注意到echopath LD_LIBRARY_PATH评估$LD_LIBRARY_PATH. 如果您的函数能够做到这一点,那就太好了,因此用户可以键入:

replace_path PATH /usr/bin /work/bin

这可以通过使用来完成:

list=$(eval echo '$'$path)

这导致了代码的此修订:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace_path         PATH /exact/path/to/remove
#    replace_path_pattern PATH <grep pattern for target path>
#
# To replace a path:
#    replace_path         PATH /exact/path/to/remove /replacement/path
#    replace_path_pattern PATH <target pattern> /replacement/path
#
###############################################################################

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 the precise string to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace_path () {
    path=$1
    list=$(eval echo '$'$path)
    remove=$2
    replace=$3            # Allowed to be empty or unset

    export $path=$(echo "$list" | tr ":" "\n" | sed "s:^$remove\$:$replace:" |
                   tr "\n" ":" | sed 's|:$||')
}

# Remove or replace an element of $1
#
#   $1 name of the shell variable to set (e.g. PATH)
#   $2 a grep pattern identifying the element to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace_path_pattern () {
    path=$1
    list=$(eval echo '$'$path)
    removepat=$2
    replacestr=$3            # Allowed to be empty or unset

    removestr=$(echo "$list" | tr ":" "\n" | grep -m 1 "^$removepat\$")
    replace_path "$path" "$removestr" "$replacestr"
}

以下修改后的测试现在也可以使用:

echo
xpath=$PATH
replace_path xpath /usr
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath /usr/bin /work/bin
echopath xpath

echo
xpath=$PATH
replace_path_pattern xpath "/usr/.*/bin" /work/bin
echopath xpath

它产生与以前相同的输出。

于 2008-11-16T15:53:15.810 回答
7

重新发布我对在 Bash 中从 $PATH 变量中删除路径的最优雅方法是什么?

#!/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=$(IFS=':';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS=':';echo "${t[*]}");
于 2008-12-17T02:18:03.340 回答
3

要删除元素,您可以使用 sed:

#!/bin/bash
NEW_PATH=$(echo -n $PATH | tr ":" "\n" | sed "/foo/d" | tr "\n" ":")
export PATH=$NEW_PATH

将从路径中删除包含“foo”的路径。

您还可以使用 sed 在给定行之前或之后插入新行。

编辑:您可以通过排序和 uniq 管道删除重复项:

echo -n $PATH | tr ":" "\n" | sort | uniq -c | sed -n "/ 1 / s/.*1 \(.*\)/\1/p" | sed "/foo/d" | tr "\n" ":"
于 2008-11-07T23:31:30.047 回答
2

“如何避免在 csh 中复制路径变量”的答案中有几个相关程序。他们更专注于确保没有重复的元素,但我提供的脚本可以用作:

export PATH=$(clnpath $head_dirs:$PATH:$tail_dirs $remove_dirs)

假设你在 $head_dirs 中有一个或多个目录,在 $tail_dirs 中有一个或多个目录,在 $remove_dirs 中有一个或多个目录,那么它使用 shell 将头部、当前和尾部部分连接成一个巨大的值,然后删除每个$remove_dirs 从结果中列出的目录(如果它们不存在则不是错误),以及消除路径中任何目录的第二次和后续出现。

这并不涉及将路径组件放置到特定位置(除了在开头或结尾,以及那些只是间接的)。从符号上讲,指定要添加新元素的位置或要替换的元素是很麻烦的。

于 2008-11-08T06:18:13.033 回答
2

请注意 bash 本身可以进行搜索和替换。它可以执行您期望的所有正常的“一次或全部”,区分大小写的选项。

从手册页:

${parameter/pattern/string}

该模式被扩展以产生一个模式,就像在路径名扩展中一样。参数被扩展,模式与其值的最长匹配被替换为字符串。如果 Ipattern 以 开头/,则所有匹配的模式都将替换为字符串。通常只替换第一场比赛。如果模式以 开头#,它必须匹配参数扩展值的开头。如果模式以 开头%,则它必须匹配参数扩展值的末尾。如果 string 为 null,则删除 pattern 的匹配项,并且/可以省略以下 pattern。如果 parameter 是@or *,则依次对每个位置参数应用替换操作,展开是结果列表。如果参数是下标的数组变量@或者 *,替换操作依次应用于数组的每个成员,展开就是结果列表。

您还可以通过将$IFS(输入字段分隔符)设置为所需的分隔符来进行字段拆分。

于 2008-11-16T17:54:29.763 回答
1

好的,感谢所有响应者。我已经准备了弗洛林答案的封装版本。第一遍看起来像这样:

# path_tools.bash
#
# A set of tools for manipulating ":" separated lists like the
# canonical $PATH variable.
#
# /bin/sh compatibility can probably be regained by replacing $( )
# style command expansion with ` ` style
###############################################################################
# Usage:
#
# To remove a path:
#    replace-path         PATH $PATH /exact/path/to/remove    
#    replace-path-pattern PATH $PATH <grep pattern for target path>    
#
# To replace a path:
#    replace-path         PATH $PATH /exact/path/to/remove /replacement/path   
#    replace-path-pattern PATH $PATH <target pattern> /replacement/path
#    
###############################################################################
# Finds the _first_ list element matching $2
#
#    $1 name of a shell variable to be set
#    $2 name of a variable with a path-like structure
#    $3 a grep pattern to match the desired element of $1
function path-element-by-pattern (){ 
    target=$1;
    list=$2;
    pat=$3;

    export $target=$(echo -n $list | tr ":" "\n" | grep -m 1 $pat);
    return
}

# Removes or replaces an element of $1
#
#   $1 name of the shell variable to set (i.e. PATH) 
#   $2 a ":" delimited list to work from (i.e. $PATH)
#   $2 the precise string to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace-path () {
    path=$1;
    list=$2;
    removestr=$3;
    replacestr=$4; # Allowed to be ""

    export $path=$(echo -n $list | tr ":" "\n" | sed "s|$removestr|$replacestr|" | tr "\n" ":" | sed "s|::|:|g");
    unset removestr
    return 
}

# Removes or replaces an element of $1
#
#   $1 name of the shell variable to set (i.e. PATH) 
#   $2 a ":" delimited list to work from (i.e. $PATH)
#   $2 a grep pattern identifying the element to be removed/replaced
#   $3 the replacement string (use "" for removal)
function replace-path-pattern () {
    path=$1;
    list=$2;
    removepat=$3; 
    replacestr=$4; # Allowed to be ""

    path-element-by-pattern removestr $list $removepat;
    replace-path $path $list $removestr $replacestr;
}

仍然需要在所有功能中进行错误捕获,并且我可能应该坚持使用重复路径解决方案。

您可以通过. /include/path/path_tools.bash在工作脚本中执行 a 并调用replace-path*函数来使用它。


我仍然愿意接受新的和/或更好的答案。

于 2008-11-08T18:46:12.993 回答
1

这很容易使用 awk。

代替

{
  for(i=1;i<=NF;i++) 
      if($i == REM) 
          if(REP)
              print REP; 
          else
              continue;
      else 
          print $i; 
}

使用它启动它

function path_repl {
    echo $PATH | awk -F: -f rem.awk REM="$1" REP="$2" | paste -sd:
}

$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_repl /bin /baz
/baz:/usr/bin:/home/js/usr/bin
$ path_repl /bin
/usr/bin:/home/js/usr/bin

附加

在给定位置插入。默认情况下,它附加在末尾。

{ 
    if(IDX < 1) IDX = NF + IDX + 1
    for(i = 1; i <= NF; i++) {
        if(IDX == i) 
            print REP 
        print $i
    }
    if(IDX == NF + 1)
        print REP
}

使用它启动它

function path_app {
    echo $PATH | awk -F: -f app.awk REP="$1" IDX="$2" | paste -sd:
}

$ echo $PATH
/bin:/usr/bin:/home/js/usr/bin
$ path_app /baz 0
/bin:/usr/bin:/home/js/usr/bin:/baz
$ path_app /baz -1
/bin:/usr/bin:/baz:/home/js/usr/bin
$ path_app /baz 1
/baz:/bin:/usr/bin:/home/js/usr/bin

删除重复项

这个保留了第一次出现。

{ 
    for(i = 1; i <= NF; i++) {
        if(!used[$i]) {
            print $i
            used[$i] = 1
        }
    }
}

像这样开始:

echo $PATH | awk -F: -f rem_dup.awk | paste -sd:

验证是否所有元素都存在

以下将为文件系统中不存在的所有条目打印一条错误消息,并返回一个非零值。

echo -n $PATH | xargs -d: stat -c %n

要简单地检查所有元素是否都是路径并获取返回码,您还可以使用test

echo -n $PATH | xargs -d: -n1 test -d
于 2008-12-06T22:00:26.967 回答
1

认为

echo $PATH
/usr/lib/jvm/java-1.6.0/bin:lib/jvm/java-1.6.0/bin/:/lib/jvm/java-1.6.0/bin/:/usr/lib/qt-3.3/bin:/usr/lib/ccache:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/tvnadeesh/bin

如果要删除 /lib/jvm/java-1.6.0/bin/ ,请执行以下操作

export PATH=$(echo $PATH | sed  's/\/lib\/jvm\/java-1.6.0\/bin\/://g')

sed将从 /lib/jvm/java-1.6.0/bin/: 获取输入echo $PATH并将其替换为空

这样你就可以删除

于 2011-09-29T07:02:09.797 回答
1
  • PATH 的顺序不被打乱
  • 优雅地处理空路径、路径中的空间等角落情况
  • dir 的部分匹配不会给出误报
  • 以适当的方式处理 PATH 头部和尾部的路径。否垃圾等。

假设你有 /foo:/some/path:/some/path/dir1:/some/path/dir2:/bar 并且你想替换 /some/path 然后它正确替换“/some/path”但留下“/ some/path/dir1" 或 "/some/path/dir2",正如您所期望的那样。

function __path_add(){  
    if [ -d "$1" ] ; then  
        local D=":${PATH}:";   
        [ "${D/:$1:/:}" == "$D" ] && PATH="$PATH:$1";  
        PATH="${PATH/#:/}";  
        export PATH="${PATH/%:/}";  
    fi  
}
function __path_remove(){  
    local D=":${PATH}:";  
    [ "${D/:$1:/:}" != "$D" ] && PATH="${D/:$1:/:}";  
    PATH="${PATH/#:/}";  
    export PATH="${PATH/%:/}";  
}  
# Just for the shake of completeness
function __path_replace(){  
    if [ -d "$2" ] ; then  
        local D=":${PATH}:";   
        if [ "${D/:$1:/:}" != "$D" ] ; then
            PATH="${D/:$1:/:$2:}";  
            PATH="${PATH/#:/}";  
            export PATH="${PATH/%:/}";  
        fi
    fi  
}  

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

于 2012-08-13T17:21:40.107 回答
1

这些天我更喜欢使用 ruby​​ 而不是 awk/sed/foo 之类的,所以这是我处理骗子的方法,

# add it to the path
PATH=~/bin/:$PATH:~/bin
export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')

创建一个函数以供重用,

mungepath() {
   export PATH=$(ruby -e 'puts ENV["PATH"].split(/:/).uniq.join(":")')
}

ruby one liner中的哈希、数组和字符串:)

于 2019-05-15T14:10:33.983 回答
0

只更改部分字符串的第一件事是 sed 替换。

例如:如果 echo $PATH => "/usr/pkg/bin:/usr/bin:/bin:/usr/pkg/games:/usr/pkg/X11R6/bin" 然后将 "/usr/bin" 更改为“/usr/local/bin”可以这样完成:

## 生成标准输出文件

## 使用“=”字符而不是斜杠(“/”),因为那会很混乱,# PATH 中应该不太可能使用替代引号字符

## 路径分隔符“:”在此处被删除和重新添加,# 可能需要在最后一个路径后多加一个冒号

回声 $PATH | sed '=/usr/bin:=/usr/local/bin:='

此解决方案替换了整个路径元素,因此如果新元素相似,则可能是多余的。

如果新的 PATH'-s 不是动态的,但总是在某个常量集中,您可以将它们保存在变量中并根据需要分配:

路径=$TEMP_PATH_1;# 命令 ... ; \n 路径=$TEMP_PATH_2; # 命令等... ;

可能不是你想的那样。bash/unix 上的一些相关命令是:

pushd popd cd ls # 单列可能 l -1A;find grep which # 可以确认该文件是您认为它来自哪里;环境类型

..所有这些以及更多对 PATH 或一般目录都有影响文本更改部分可以通过多种方式完成!

选择的任何解决方案都包含 4 个部分:

1)按原样获取路径 2)解码路径以找到需要更改的部分 3)确定需要哪些更改/集成这些更改 4)验证/最终集成/设置变量

于 2008-11-08T00:15:15.183 回答
0

根据dj_segfault 的回答,我在脚本中执行此操作,这些脚本附加/前置可能多次执行的环境变量:

ld_library_path=${ORACLE_HOME}/lib
LD_LIBRARY_PATH=${LD_LIBRARY_PATH//${ld_library_path}?(:)/}
export LD_LIBRARY_PATH=${ld_library_path}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}

考虑到 shell 参数扩展的类似文件名扩展的模式匹配和模式列表支持,使用相同的技术来删除、替换或操作 PATH 中的条目是微不足道的。

于 2014-09-09T16:19:45.343 回答