私の答えのような絶対パスのソートされたリストがあるとしましょう here (この質問のために短縮され、変更されました):
/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
どのようにこれをコマンドラインで簡単に行うことができますか? Bash、sed
、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代替案でも同じアプローチが使用されます。
#!/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,'')
新しいpathlib
ライブラリを使用する別のPythonバージョン:
#! /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))