Bash函数从POSIX PATH

时间:2017-09-03 02:17:24

标签: bash shell

我需要一个函数来从POSIX PATH(和MANPATH等)中删除重复项,所以我写了这个。我是bash的新手,在$ PATH中犯错并不好。我知道它在Windows PATH上是barf。我的报价好吗?如果它是你的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

我关注的是空间和贝壳元,我不知道自己是否保护自己。

此外,这取决于declare -A。我怎么能在bash-3中做到这一点?

P.S。这个例子是为了测试而伪造的。我的道路上并没有/tmp。而且,我很少/无法控制它是如何在第一时间翘起来的;我只是想把它修好。

2 个答案:

答案 0 :(得分:2)

在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%:}"
}

注意:

  • 所有扩展都是引用的,或者是在明确禁止字符串拆分和glob扩展的上下文中。设置IFS=:可以解决一些最明显的缺失引号陷阱,但绝不会解决所有问题。
  • IFS=: read -r -a用于读入显式用作分隔符:的数组。这仅对单个命令修改IFS,对任何其他范围没有影响(并且不依赖于local IFS按预期工作;我清楚地记得看到它没有的贝壳,尽管我需要深入了解确切的位置。
  • 没有理由return 0,这样做会适得其反。默认返回值是最后一个命令的退出状态。如果printf失败(可能是因为您的stdout是关闭或无效的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 :(得分: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";