我需要一个函数来从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
。而且,我很少/无法控制它是如何在第一时间翘起来的;我只是想把它修好。
答案 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%:}"
}
注意:
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";