Bash 函数从 POSIX PATH 中删除重复项

问题描述 投票:0回答:3

我需要一个函数来从我的 POSIX PATH(和 MANPATH 等)中删除重复项,所以我写了这个。我是 bash 新手,在 $PATH 中犯错误并不好。我知道它会在 Windows PATH 上呕吐。我的报价可以吗?如果它是你的 root shell,你会改变什么?

fixpath() {

    local IFS=:
    local -A alreadyDone
    local -a newpath
    local -i i
    local frag
    i=0

    for frag in $@ ; do
    [ ${alreadyDone[$frag]+abc} ] || {
        alreadyDone[$frag]=$frag;
        newpath[$i]=$frag;
        ((i++))
    }
    done
    printf '%s\n' "${newpath[*]}"
    return 0
}

这是一些示例输出:

-bash-4.4$ fixpath /var/tmp:/home/bennett/bin:/home/bennett/bin:/usr/oneworld/bin:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/games:/snap/bin:/home/bennett/bin:/home/bennett/bin:/usr/oneworld/bin:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/games:/snap/bin:/tmp:/home/bennett/bin:/home/bennett/bin:/usr/oneworld/bin:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/games:/snap/bin         

/var/tmp:/home/bennett/bin:/usr/oneworld/bin:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/games:/snap/bin:/tmp

我关心的是空格和 shell 元字符,我不知道我是否保护了自己。

此外,这依赖于

declare -A
。我该如何在 bash-3 中执行此操作?

附注该示例是为了测试而伪造的。我的道路上并没有真正的

/tmp
。而且,我几乎无法控制它是如何被搞砸的;我只是想把它修好。

bash shell
3个回答
3
投票

在 bash 4 中,我将按如下方式实现:

fixpath() {
    local out_str
    local -a pieces=( ) out=( )
    local -A seen=( )

    IFS=: read -a pieces <<<"$1"

    for piece in "${pieces[@]}"; do
      [[ ${seen[$piece]} ]] && continue
      out+=( "$piece" )
      seen[$piece]=1
    done

    printf -v out_str '%s:' "${out[@]}"
    printf '%s\n' "${out_str%:}"
}

注:

  • 所有扩展要么被引用,要么在明确禁止字符串分割和全局扩展的上下文中。设置
    IFS=:
    可以解决一些最明显的缺少引号的陷阱,但绝不能解决所有问题。
  • IFS=: read -r -a
    用于读入数组,并明确使用
    :
    作为分隔符。这仅修改单个命令的
    IFS
    ,对任何其他范围没有影响(并且不依赖于
    local IFS
    按预期工作;我清楚地记得看到了 shell,但它没有,尽管我需要挖掘找出它到底在哪里)。
  • 没有理由
    return 0
    ,这样做会适得其反。默认返回值为最后一个命令的退出状态。如果
    printf
    失败(可能是因为您的标准输出是已关闭或无效的 FD),您 不应该 返回成功状态。

在 bash 3 中,如果没有关联数组,效率就会显着降低。幸运的是,PATH 通常足够短,O(n^2) 不会令人望而却步(仍然比启动外部解释器在 awk 或 perl 中完成工作更快):

fixpath() {
    local out_str seen
    local -a pieces=( ) out=( )

    IFS=: read -a pieces <<<"$1"

    for piece in "${pieces[@]}"; do
      seen=0
      for out_piece in "${out[@]}"; do
        [[ "$out_piece" = "$piece" ]] && { seen=1; break; }
      done
      (( seen )) && continue
      out+=( "$piece" )
    done

    printf -v out_str '%s:' "${out[@]}"
    printf '%s\n' "${out_str%:}"
}

1
投票

使用 Perl 可能更容易做到:

#!/usr/bin/perl -w

use strict;

my $input = shift;
my %seen;

foreach my $dir ( split /:/, $input ) {
        $seen{$dir} = 1;
}
my $output = join( ':', keys(%seen));
print $output . "\n";

您不必担心处理目录名称中的空格。

$ ./fixpath.pl "/var/tmp:/home/bennett/bin:/home/bennett/bin:/usr/oneworld/bin:/usr/local/bin:/path with space/dir:/usr/local/bin"
/var/tmp:/path with space/dir:/home/bennett/bin:/usr/local/bin:/usr/oneworld/bin

如果您想确保不更改路径的顺序,请执行以下操作:

#!/usr/bin/perl -w

use strict;

my $input = shift;
my %seen;

my $order=1;
foreach my $dir ( split /:/, $input ) {
        $seen{$dir} = $order++ unless ($seen{$dir})  ;
}
my $output =  join( ':',  sort { $seen{$a} <=> $seen{$b} } keys(%seen));
print $output . "\n";

0
投票

我正在查看

here
中的 pathappend() 函数,并将其修改为使用
NEWPATH
而不是
PATH

unset NEWPATH
for i in $(echo $PATH | tr ':' ' '); do
  [ -d "$i" ] && [[ ":$NEWPATH:" != *":$i:"* ]] && NEWPATH="${NEWPATH:+"$NEWPATH:"}$i"
done
export PATH=$NEWPATH

基本上,循环路径段,然后将每个路径段添加到新变量

NEWPATH
(如果尚未添加)。最后,将
PATH
设置为
NEWPATH
。它还会检查每个路径段是否是真实目录,然后再将其添加回来。

© www.soinside.com 2019 - 2024. All rights reserved.