删除重复的 PATH 条目
本文的目标是删除 PATH 变量中的重复条目。但在我开始之前,先明确一点:没有令人信服的理由要这样做。实际上,shell 会忽略重复的 PATH 条目;只有任何路径的第一次出现才是重要的。进行此练习的两个动机。第一个是研究一个awk最初看起来并没有真正做多少事情的单行命令。第二个是为了满足那些对拥有重复 PATH 条目感到恼火的人的需求。
当我使用 Cygwin 时,我第一次产生了这样做的冲动。在 Windows 上,几乎每个可执行文件都放在不同的目录中,您的 PATH 变量很快就会变得不堪重负,因此删除重复项可以使您在尝试 decipher 您的 PATH 变量中实际包含的内容时稍微不那么困惑。
您关于如何执行此操作的第一个想法可能是使用以下命令将路径分解为各个元素:sed然后通过sort和uniq来消除重复项。但是您很快就会意识到这行不通,因为您现在已经重新排序了路径,而您不希望这样做。您希望保持路径的原始顺序,只是删除重复项。
这个最初的想法不是我的。我在互联网上找到了它的基本代码。我不记得确切的位置了,但我相信它是在 Stack Exchange 上。最初的 bash/awk 代码如下所示:
PATH=$(echo $PATH | awk -v RS=: -v ORS=: '!($0 in a) {a[$0]; print}')
它很接近了。它几乎可以工作,但在查看输出之前,让我们看看它为什么/如何工作。为此,首先请注意-v选项。这些选项设置了 awk 用于将输入数据分隔为单独的 记录 数据以及如何在输出时重新组装它们的输入和输出 记录分隔符 变量。默认情况下,它们由换行符分隔——也就是说,输入的每一行都是一个单独的记录。让我们使用冒号作为分隔符,而不是换行符,这将 PATH 变量中的每个单独路径作为一个单独的记录。您可以在以下示例中看到它的工作原理,您只更改了输入分隔符,并将输出分隔符保留为换行符,并提出了一个简单的 awk 单行命令,用于在单独的行上打印路径的每个元素:
$ cat showpath.sh
export PATH=/usr/bin:/bin:/usr/local/bin:/usr/bin:/bin
awk -v RS=: '{print}' <<<$PATH
$ bash showpath.sh
/usr/bin
/bin
/usr/local/bin
/usr/bin
/bin
所以,回到原始代码。为了帮助理解它,让我们通过重新格式化使其看起来更 awkish 一点,使其具有更正常的pattern { action }或condition { action }外观
!($0 in a) {
a[$0];
print
}
这里的 condition 是!($0 in a)。 在这里,$0$0是当前的输入记录,而a是一个 awk 变量(使用in是当前的输入记录,而运算符,告诉您a是一个数组)。 请记住,每个 输入记录 都是 PATH 变量中的一个单独路径。 括号内的部分,是当前的输入记录,而$0 in a是当前的输入记录,而测试路径是否在数组中是当前的输入记录,而a
。 感叹号和括号用于否定条件。 因此,如果当前路径不在!($0 in a)a中,则执行操作。 如果当前路径在a中,则不执行操作,并且由于这就是脚本的全部内容,因此在这种情况下不会发生任何事情。如果当前路径不在数组中,则操作中的代码使用该路径作为键来引用数组。 在 awk 中,数组是关联数组,并且引用关联数组中不存在的元素会自动创建该元素。 通过在数组中创建元素,您现在已设置数组,以便下次看到相同的路径元素时,您的条件
$ cat nodupes.sh
export PATH=/usr/bin:/bin:/usr/local/bin:/usr/bin:/bin
echo $PATH | awk -v RS=: -v ORS=: '!($0 in a) {a[$0]; print}'
$ bash nodupes.sh
/usr/bin:/bin:/usr/local/bin:/bin
:
!($0 in a)将失败并且操作将不会执行。 换句话说,该操作仅在您第一次看到路径时执行。 最后,在引用数组之后,您打印当前路径,并且 awk 会自动添加输出分隔符。 请注意,空的print等效于和print $0。 让我们看看它的实际效果:正如我所说,它几乎可以工作。 唯一的问题是以下行上有一个额外的换行符和一个额外的冒号。 额外的换行符来自以下事实:echo
$ cat nodupes2.sh
export PATH=/usr/bin:/bin:/usr/local/bin:/usr/bin:/bin
echo -n $PATH | awk -v RS=: -v ORS=: '!($0 in a) {a[$0]; print $0}'
$ bash nodupes2.sh
/usr/bin:/bin:/usr/local/bin:
正在路径末尾添加一个换行符,并且由于 awk 没有将换行符视为分隔符,因此它被添加到最后一个路径的末尾,在这种情况下,这会导致它看起来像 awk 未能删除重复项。 但是 awk 没有将它们视为重复项,而是看到了/bin/bin\n中,则执行操作。 如果当前路径在。 您可以使用
$ cat nodupes3.sh
export PATH=/usr/bin:/bin:/usr/local/bin:/usr/bin:/bin
echo -n $PATH | awk -v RS=: '!($0 in a) {a[$0]; printf("%s%s", length(a) > 1 ? ":" : "", $0)}'
$ bash nodupes3.sh
/usr/bin:/bin:/usr/local/bin
-n选项消除尾随换行符echo您几乎就在那里了,除了尾随冒号,这实际上不是问题。 空的 PATH 元素将被忽略,但是既然您已经在这段有点毫无意义的旅程中走了这么远,您不妨走完全程。 要解决此问题,请使用 awk 的printf
命令而不是