解决 dmckee 提出的解决方案:
- 虽然某些版本的 Bash 可能允许在函数名称中使用连字符,但其他版本 (MacOS X) 则不允许。
- 我认为不需要在函数结束之前立即使用 return。
- 我认为不需要所有分号。
- 我不明白为什么您要逐个路径元素导出一个值。将
export
其视为等效于设置(甚至创建)全局变量 - 尽可能避免的事情。
- 我不确定您希望 '
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
它产生与以前相同的输出。