移除重复的 PATH 条目:重启
在我的关于移除重复 PATH 条目的第一篇文章中,我使用了一个 AWK 单行命令。在第二篇文章中,我使用了一个 Perl 单行命令,或者更准确地说,我试图剖析读者 Shaun 提供的一个 Perl 单行命令。Shaun 曾问我,如果我愿意使用 AWK(而不是 Bash),为什么不使用 Perl 呢?我突然想到,有人也可能会问:为什么不直接使用 Bash 呢?所以,再一次深入探讨。
对于那些读完第二篇文章的人来说,不用担心;这篇文章相比之下应该会非常简短。虽然我可以通过使用大量分号将其变成单行命令,但我不会这样做。
方法与 AWK 和 Perl 代码版本中的方法基本相同:用冒号分割路径,使用关联数组来确定是否之前已经见过某个路径元素,然后用冒号将非重复的路径元素重新连接在一起。
请注意,我在之前的文章中错误地声明,在此过程中也可以消除空路径元素。实际上,空路径元素与在路径中放置“.”是相同的;它表示当前目录。我的这个错误是在第二篇文章的评论中被指出的。我想我应该读一下手册页。
为了将 PATH 分割成各个元素,我将 bash 的记录分隔符更改为冒号,然后将 PATH 变量赋值给一个数组
IFS=: ipaths=($PATH)
请注意在同一行中对 IFS
的赋值和对数组的赋值;如果您以前没见过,这是标准的 bash 语法
一个简单命令是一系列可选的变量赋值,后跟空格分隔的单词和重定向,并以控制运算符终止。
现在我的路径元素都在 paths
变量中了,我启动一个关联数组并测试每个元素,看看它是否在数组中(再次提醒,在 bash 中,不存在的数组元素将评估为空白)
declare -A a # Need to declare the array as associative
for p in "${ipaths[@]}"
do
[[ -z "${a[$p]}" ]] && a[$p]=1 && opaths+=":$p"
done
循环遍历 ipaths
数组中的每个路径元素,并在名为 opaths
的变量中创建新路径。循环体测试当前路径元素的数组条目是否为空白([[ -z "${a[$p]}" ]]
),这意味着该路径之前没有见过。如果之前没有见过,它会将路径元素的数组条目设置为非空白值(a[$p]=1
),然后添加路径元素,即包含输出路径的变量 opaths+=":$p"
(此处添加冒号)。
不幸的是,这对于空白路径元素会失败:bash 中不允许空白关联数组键。为了解决这个问题,我将使用一个单独的变量来确定是否之前已经见过空白路径。另一种选择是在处理路径之前将“::”更改为类似“*CURRENT_DIR*”的内容,然后在之后改回来。新的循环看起来像这样
declare -A a
for p in "${ipaths[@]}"
do
if [[ -z "$p" ]]; then
[[ -z "$currdir" ]] && currdir=1 && opaths+=":"
else
[[ -z "${a[$p]}" ]] && a[$p]=1 && opaths+=":$p"
fi
done
由于所有输出路径都以冒号开头,即使是第一个,最终输出路径也需要删除第一个冒号。我使用一个简单的子字符串评估来完成此操作
export PATH="${opaths:1}"
简短而精悍。它不如 Perl 版本或原始 AWK 版本那么短,但我可以将上面的代码放入一个 bash 函数中,并假装这个版本真的很短
export PATH="$(remove_path_dupes)"
我只能希望这是我最后一次写关于从 PATH 变量中删除重复项的文章,但我不能做出任何保证。
编辑:以下是我原始帖子的附录。
读者“pepa65”在之前的帖子中评论说,提出了另一个全 bash 解决方案,坦率地说,这比我的解决方案更简洁
IPATH='/usr/bin:/usr/local/bin::/usr/bin:/some folder/j:'
OPATH=$(n= IFS=':'; for e in $IPATH; do [[ :$n == *:$e:* ]] || n+=$e:; done; echo "${n:0: -1}")
echo $IPATH
echo $OPATH
为了更容易看清楚,我将展开命令替换内部的部分($(...)
表达式)
n= IFS=':'
for e in $IPATH
do
[[ :$n == *:$e:* ]] || n+=$e:
done
echo "${n:0: -1}"
它没有使用关联数组来查看路径元素是否在输出路径中,而是简单地使用 glob 比较来查看是否已经在输出路径变量中找到该路径元素。
起初您可能会想知道这为什么能行得通,因为在关于路径名扩展(又名 glob)的部分中,bash 手册页指出
在单词分割之后,除非设置了 -f 选项,否则 bash 会扫描每个单词中的字符 *、? 和 [。如果出现这些字符之一,则该单词将被视为模式,并替换为与该模式匹配的文件名的字母排序列表...
我们当然不希望模式中的星号扩展为文件名列表。但这不用担心,因为在关于 [[ expression ]]
评估的部分中,手册页指出
... 单词分割和路径名扩展不会在 [[ 和 ]] 之间的单词上执行 ...
因此,路径名扩展不会发生。要了解它实际工作的原因,请继续阅读 [[ expression ]]
部分,再往下一点您会看到
当使用 == 和 != 运算符时,运算符右侧的字符串被视为模式,并根据下面“模式匹配”下描述的规则进行匹配,就好像启用了 extglob shell 选项一样。...
请注意,与 bash 正则表达式运算符 =~
类似,当涉及到模式匹配时,这些运算符不是对称的,因此以下方法不起作用
[[ *:$e:* == :$n ]] || n+=$e:
很棒的解决方案!我宣布它是获胜者。