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

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

问题描述

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

这是我想要的

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

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

我知道我已经完成了这项工作,并且可能可以自己拼凑一些东西,但我正在寻找一种不错的方法.便携性和标准化加分项.

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

<小时>

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

<小时>

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

<小时>

解决方案

解决 dmckee 提出的解决方案:

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

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

<预><代码>./用户/jleffler/bin/usr/local/postgresql/bin/usr/local/mysql/bin/用户/jleffler/perl/v5.10.0/bin/usr/本地/bin/usr/bin/bin/sw/bin/usr/sbin/sbin

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

<预><代码>./用户/jleffler/bin/本地/postgresql/bin/本地/mysql/bin/用户/jleffler/perl/v5.10.0/bin/本地/斌/bin/bin/sw/bin/sbin/sbin

我本来希望恢复我的原始路径,因为/usr 不作为(完整的)路径元素出现,而只是作为路径元素的一部分.

这可以通过修改 sed 命令之一在 replace-path 中修复:

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

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

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

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

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

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

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

# path_tools.bash## 一组用于操作:"分隔列表的工具,如# 规范的 $PATH 变量.##/bin/sh 兼容性可能可以通过替换 $( )# 使用 ` ` 样式的样式命令扩展############################################################################ 用法:## 删除路径:# replace_path PATH $PATH/exact/path/to/remove# replace_path_pattern PATH $PATH <目标路径的grep模式>## 替换路径:# replace_path PATH $PATH/exact/path/to/remove/replacement/path# replace_path_pattern PATH $PATH <目标模式>/替换/路径############################################################################# 删除或替换 $1 的元素## $1 要设置的 shell 变量的名称(例如 PATH)# $2 一个:"分隔的列表(例如 $PATH)# $3 要删除/替换的精确字符串# $4 替换字符串(使用 "" 删除)函数replace_path(){路径=$1列表=$2移除=$3replace=$4 # 允许为空或未设置export $path=$(echo "$list" | tr ":" "
" | sed "s:^$remove$:$replace:" |tr "
" ":" |sed 's|:$||')}# 删除或替换 $1 的元素## $1 要设置的 shell 变量的名称(例如 PATH)# $2 一个:"分隔的列表(例如 $PATH)# $3 一个 grep 模式标识要删除/替换的元素# $4 替换字符串(使用 "" 删除)函数replace_path_pattern(){路径=$1列表=$2removepat=$3replacestr=$4 # 允许为空或未设置removestr=$(echo "$list" | tr ":" "
" | grep -m 1 "^$removepat$")replace_path "$path" "$list" "$removestr" "$replacestr"}

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

#!/usr/bin/perl -w## "@(#)$Id: echopath.pl,v 1.7 1998/09/15 03:16:36 jleffler Exp $"## 每行打印一个 PATH 变量的组件.# 如果参数中没有冒号,假设它们是# 环境变量的名称.@ARGV = $ENV{PATH} 除非@ARGV;foreach $arg (@ARGV){$var = $arg;$var = $ENV{$arg} if $arg =~/^[A-Za-z_][A-Za-z_0-9]*$/;$var = $arg 除非 $var;@lst = 拆分/:/, $var;foreach $val (@lst){打印 "$val
";}}

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

回声xpath=$PATHreplace_path xpath $xpath/usr回声路径 $xpath回声xpath=$PATHreplace_path_pattern xpath $xpath/usr/bin/work/bin回声路径 xpath回声xpath=$PATHreplace_path_pattern xpath $xpath "/usr/.*/bin"/work/bin回声路径 xpath

输出为:

<预><代码>./用户/jleffler/bin/usr/local/postgresql/bin/usr/local/mysql/bin/用户/jleffler/perl/v5.10.0/bin/usr/本地/bin/usr/bin/bin/sw/bin/usr/sbin/sbin./用户/jleffler/bin/usr/local/postgresql/bin/usr/local/mysql/bin/用户/jleffler/perl/v5.10.0/bin/usr/本地/bin/工作/垃圾箱/bin/sw/bin/usr/sbin/sbin./用户/jleffler/bin/工作/垃圾箱/usr/local/mysql/bin/用户/jleffler/perl/v5.10.0/bin/usr/本地/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## 一组用于操作:"分隔列表的工具,如# 规范的 $PATH 变量.##/bin/sh 兼容性可能可以通过替换 $( )# 使用 ` ` 样式的样式命令扩展############################################################################ 用法:## 删除路径:#replace_path PATH/exact/path/to/remove# replace_path_pattern PATH <目标路径的grep模式>## 替换路径:#replace_path PATH/exact/path/to/remove/replacement/path# replace_path_pattern PATH <目标模式>/替换/路径############################################################################# 删除或替换 $1 的元素## $1 要设置的 shell 变量的名称(例如 PATH)# $2 要删除/替换的精确字符串# $3 替换字符串(使用 "" 删除)函数replace_path(){路径=$1list=$(eval echo '$'$path)移除=$2replace=$3 # 允许为空或未设置export $path=$(echo "$list" | tr ":" "
" | sed "s:^$remove$:$replace:" |tr "
" ":" |sed 's|:$||')}# 删除或替换 $1 的元素## $1 要设置的 shell 变量的名称(例如 PATH)# $2 一个 grep 模式标识要删除/替换的元素# $3 替换字符串(使用 "" 删除)函数replace_path_pattern(){路径=$1list=$(eval echo '$'$path)removepat=$2replacestr=$3 # 允许为空或未设置removestr=$(echo "$list" | tr ":" "
" | grep -m 1 "^$removepat$")replace_path "$path" "$removestr" "$replacestr"}

以下修改后的测试现在也有效:

回声xpath=$PATH替换路径 xpath/usr回声路径 xpath回声xpath=$PATHreplace_path_pattern xpath/usr/bin/work/bin回声路径 xpath回声xpath=$PATHreplace_path_pattern xpath "/usr/.*/bin"/work/bin回声路径 xpath

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

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

That is I want to take

PATH=/home/joe/bin:/usr/local/bin:/usr/bin:/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.

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


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.


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


解决方案

Addressing the proposed solution from dmckee:

  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.

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

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

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.

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

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

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

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

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

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

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 ":" "
" | sed "s:^$remove$:$replace:" |
                   tr "
" ":" | 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 ":" "
" | grep -m 1 "^$removepat$")
    replace_path "$path" "$list" "$removestr" "$replacestr"
}

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

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

The output is:

.
/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.

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)

This leads to this revision of the code:

# 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 ":" "
" | sed "s:^$remove$:$replace:" |
                   tr "
" ":" | 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 ":" "
" | 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.

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

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