如何在shell脚本中操作$ PATH元素?

时间:2008-11-07 22:52:15

标签: unix shell idioms path-variables

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

那就是我想要

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

删除替换 /path/to/app/bin,而不会破坏变量的其余部分。允许我新元素放在任意位置的额外点数。目标将通过定义良好的字符串识别,并且可以在列表中的任何位置发生。

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

我使用bash,但欢迎你最喜欢的shell中的例子。


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


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


12 个答案:

答案 0 :(得分:19)

从dmckee处理建议的解决方案:

  1. 虽然某些版本的Bash可能允许在函数名称中使用连字符,但其他版本(MacOS X)则不允许使用连字符。
  2. 我认为不需要在函数结束前立即使用return。
  3. 我认为不需要所有的冒号。
  4. 我不明白为什么你有一个逐个模式的路径导出值。可以认为export等同于设置(甚至创建)全局变量 - 尽可能避免这种情况。
  5. 我不确定你对'replace-path PATH $PATH /usr'的期望是什么,但它并没有达到我的预期。
  6. 考虑一个开头的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的{​​{1}}选项也是非标准的;你最好只删除由于将换行符从echo转换为冒号而添加的尾部冒号。由于path-element-by-pattern只使用一次,因此会产生不良副作用(它会破坏任何预先存在的名为echo的导出变量),它可以被其身体明智地替换掉。这一点,以及更自由地使用引号来避免空格或不需要的文件名扩展问题,导致:

    $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脚本,在调试类似PATH的变量时我觉得很有用:

    echopath

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

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

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

答案 1 :(得分:5)

将我的回答重新发送到What is the most elegant way to remove a path from the $PATH variable in 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[*]}"

或单行:

PATH=$(IFS=':';t=($PATH);unset IFS;t=(${t[@]%%*usr*});IFS=':';echo "${t[*]}");

答案 2 :(得分: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" ":"

答案 3 :(得分:2)

How to keep from duplicating path variable in csh”的答案中有几个相关的程序。他们更专注于确保没有重复的元素,但我提供的脚本可以用作:

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

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

这不涉及将路径组件放入特定位置(除了开头或结尾,而只是间接)。在符号上,指定要添加新元素的位置或要替换的元素是混乱的。

答案 4 :(得分:2)

请注意,bash本身可以进行搜索和替换。它可以完成你期望的所有正常“一次或全部”,[在]敏感选项中的情况。

从手册页:

$ {parameter / pattern / string}

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

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

答案 5 :(得分:1)

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

# 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并调用replace-path*函数来使用它。


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

答案 6 :(得分: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

答案 7 :(得分: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将从echo $PATH获取输入并将/lib/jvm/java-1.6.0/bin/替换为空

以这种方式你可以删除

答案 8 :(得分:1)

  • PATH顺序未被分发
  • 处理角落案件,如空路径,路径优雅的空间
  • dir的部分匹配不会产生误报
  • 以适当的方式处理PATH头部和尾部的路径。没有垃圾等。

说你有     /富:/一些/路径:/一些/路径/ DIR1:/一些/路径/ DIR2:/巴 而你想要替换     /一些/路径 然后它正确地替换“/ 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  
}  

相关文章 What is the most elegant way to remove a path from the $PATH variable in Bash?

答案 9 :(得分: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(":")')
}

一个红宝石衬里中的哈希,数组和字符串:)

答案 10 :(得分:0)

首先要改变一下字符串的一部分,就是sed替换。

例如: 如果echo $ PATH =&gt; “在/ usr /包装/斌:在/ usr / bin中:/ bin中:在/ usr /包装/游戏:在/ usr /包装/ X11R6 / bin中” 然后将“/ usr / bin”更改为“/ usr / local / bin”可以这样做:

##生成标准输出文件

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

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

echo $ PATH | sed'= / usr / bin:= / usr / local / bin:='

此解决方案替换整个路径元素,因此如果new-element类似,则可能是多余的。

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

PATH = $ TEMP_PATH_1;  #command ...; \ n PATH = $ TEMP_PATH_2;  #command etc ...;

可能不是你在想什么。 bash / unix上的一些相关命令是:

PUSHD POPD 光盘 ls#可能l-1A为单列; 找 grep的 #可以确认该文件是您认为它来自哪里; ENV 型

..而且所有这些以及更多某些都会对PATH或目录产生影响。文本修改部分可以通过多种方式完成!

无论选择哪种解决方案都有4个部分:

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

答案 11 :(得分:0)

dj_segfault's answer一致,我在附加/预置可能多次执行的环境变量的脚本中执行此操作:

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

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