如何在外壳程序脚本中操作$ PATH元素? [英] How do I manipulate $PATH elements in shell scripts?

查看:70
本文介绍了如何在外壳程序脚本中操作$ PATH元素?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Is there a idiomatic way of removing elements from PATH-like shell variables?

那是我想要的

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

删除替换 /path/to/app/bin,而不会破坏其余的变量.允许我在任意位置放入新元素的加分.目标可以通过定义良好的字符串来识别,并且可以在列表中的任何位置出现.

and remove or replace the /path/to/app/bin without clobbering the rest of the variable. Extra points for allowing me put new elements in arbitrary positions. The target will be recognizable by a well defined string, and may occur at any point in the list.

我知道我已经看到了这一点,并且可以独自完成一些工作,但是我正在寻找一种不错的方法.可移植性和标准化是一个加分.

I know I've seen this done, and can probably cobble something together on my own, but I'm looking for a nice approach. Portability and standardization a plus.

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

I use bash, but example are welcome in your favorite shell as well.

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

The context here is one of needing to switch conveniently between multiple versions (one for doing analysis, another for working on the framework) of a large scientific analysis package which produces a couple dozen executables, has data stashed around the filesystem, and uses environment variable to help find all this stuff. I would like to write a script that selects a version, and need to be able to remove the $PATH elements relating to the currently active version and replace them with the same elements relating to the new version.

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

This is related to the problem of preventing repeated $PATH elements when re-running login scripts and the like.

  • Previous similar question: How to keep from duplicating path variable in csh
  • Subsequent similar question: What is the most elegant way to remove a path from the $PATH variable in Bash?

推荐答案

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

Addressing the proposed solution from dmckee:

  1. 虽然某些版本的Bash可能允许在函数名称中使用连字符,但其他版本(MacOS X)则不允许.
  2. 我认为不需要在函数结束前立即使用return.
  3. 我看不到所有分号都需要.
  4. 我不明白为什么您要按路径逐个元素导出一个值.认为export等效于设置(甚至创建)全局变量-在任何可能的情况下都应避免.
  5. 我不确定您希望'replace-path PATH $PATH /usr'做什么,但是它并没有达到我的期望.
  1. While some versions of Bash may allow hyphens in function names, others (MacOS X) do not.
  2. I don't see a need to use return immediately before the end of the function.
  3. I don't see the need for all the semi-colons.
  4. I don't see why you have path-element-by-pattern export a value. Think of export as equivalent to setting (or even creating) a global variable - something to be avoided whenever possible.
  5. I'm not sure what you expect 'replace-path PATH $PATH /usr' to do, but it does not do what I would expect.

考虑一个开始于以下内容的PATH值:

Consider a PATH value that starts off containing:

.
/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"获得的结果是:

The result I got (from 'replace-path PATH $PATH /usr') is:

.
/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不会显示为(完整的)路径元素,而只是显示为路径元素的一部分,所以我本来希望恢复原来的路径.

I would have expected to get my original path back since /usr does not appear as a (complete) path element, only as part of a path element.

可以通过修改sed命令之一来在replace-path中修复该问题:

This can be fixed in replace-path by modifying one of the sed commands:

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

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

I used ':' instead of '|' to separate parts of the substitute since '|' could (in theory) appear in a path component, whereas by definition of PATH, a colon cannot. I observe that the second sed could eliminate the current directory from the middle of a PATH. That is, a legitimate (though perverse) value of PATH could be:

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

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

After processing, the current directory would no longer be on the PATH.

path-element-by-pattern中进行锚定匹配的类似更改是合适的:

A similar change to anchor the match is appropriate in path-element-by-pattern:

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

我注意到grep -m 1不是标准的(它是GNU扩展,也可在MacOS X上使用).而且,的确,echo-n选项也是非标准的.您最好只删除通过将换行符从echo转换为冒号而添加的结尾冒号.由于按模式使用逐个路径的路径,具有不良的副作用(它掩盖了任何先前存在的称为$removestr的导出变量),因此可以用其主体明智地替换它.加上更广泛地使用引号来避免空格或不必要的文件名扩展问题,会导致:

I note in passing that grep -m 1 is not standard (it is a GNU extension, also available on MacOS X). And, indeed, the-n option for echo is also non-standard; you would be better off simply deleting the trailing colon that is added by virtue of converting the newline from echo into a colon. Since path-element-by-pattern is used just once, has undesirable side-effects (it clobbers any pre-existing exported variable called $removestr), it can be replaced sensibly by its body. This, along with more liberal use of quotes to avoid problems with spaces or unwanted file name expansion, leads to:

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

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

I have a Perl script called echopath which I find useful when debugging problems with PATH-like variables:

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

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

When I run the modified solution on the test code below:

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

这对我来说似乎是正确的-至少对于我对问题的定义而言.

This looks correct to me - at least, for my definition of what the problem is.

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

I note that echopath LD_LIBRARY_PATH evaluates $LD_LIBRARY_PATH. It would be nice if your functions were able to do that, so the user could type:

replace_path PATH /usr/bin /work/bin

可以使用以下方法完成:

That can be done by using:

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

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

The following revised test now works too:

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

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

It produces the same output as before.

这篇关于如何在外壳程序脚本中操作$ PATH元素?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆