假设我有一个绝对路径的排序列表,就像我的答案中的列表一样这里(针对该问题进行了缩短和修改):
/proc
/proc/sys/fs/binfmt_misc
/proc/sys/fs/binfmt_misc
/run
/run/cgmanager/fs
/run/hugepages/kvm
/run/lock
/run/user/1000
/run/user/1000/gvfs
/tmp
/home/bytecommander/ramdisk
我想要的是通过消除所有先前提到的路径的子目录来减少此列表。这意味着,对于给定的输入,我想要以下输出:
/proc
/run
/tmp
/home/bytecommander/ramdisk
sed
如何使用 Bash或其他常用工具在命令行中轻松完成此操作awk
?一行简短的解决方案值得赞赏,但不是必需的。
答案1
大王
$ awk -F '/' 'oldstr && NR>1{ if($0!~oldstr"/"){print $0;oldstr=$0}};NR == 1{print $0;oldstr=$0}' paths.txt
/proc
/run
/tmp
/home/bytecommander/ramdisk
/var/zomg
/var/zomgkthx
/zomg
/zomgkthx
其工作方式非常简单,但命令的顺序很重要。我们首先记录第一行的内容并将其打印出来。我们转到下一行并检查下一行是否包含先前的文本。如果包含 - 我们什么也不做。如果不包含 - 那就是一条不同的新路径。
原始方法存在缺陷,当相邻路径具有相同的前导子字符串时会失败,例如/var/zomg
和/var/zomgkthx
(感谢 Chai T.Rex 指出这一点)。诀窍是在旧路径后附加“/”以表示其结束,从而破坏子字符串。下面的 Python 替代方案中使用了相同的方法。
Python 替代品
#!/usr/bin/env python
import sys,os
oldline = None
with open(sys.argv[1]) as f:
for index,line in enumerate(f):
path = line.strip()
if index == 0 or not line.startswith(oldline):
print(path)
oldline = os.path.join(path,'')
示例运行:
$ ./reduce_paths.py paths.txt
/proc
/run
/tmp
/home/bytecommander/ramdisk
/var/zomg
/var/zomgkthx
/zomg
/zomgkthx
这种方法类似于 awk-one。思路是一样的:记录第一行,只有当我们遇到没有跟踪变量作为起始子字符串的行时才继续打印并重置跟踪变量。
或者,也可以使用os.path.commonprefix()
函数。
#!/usr/bin/env python
import sys,os
oldline = None
with open(sys.argv[1]) as f:
for index,line in enumerate(f):
path = line.strip()
if index == 0 or os.path.commonprefix([path,oldline]) != oldline:
print(path)
oldline = os.path.join(path,'')
答案2
另一个 Python 版本,使用新pathlib
库:
#! /usr/bin/env python3
import pathlib, sys
seen = set()
for l in sys.stdin:
p = pathlib.Path(l.strip())
if not any(x in seen for x in p.parents):
seen.add(p)
print(str(p))