Disons que j'ai une liste triée de chemins absolus, comme celle de ma réponse ici (abrégée et modifiée pour cette question):
/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
Ce que je veux, c'est réduire cette liste en éliminant tous les chemins qui sont des sous-répertoires des chemins mentionnés précédemment. Cela signifie que, pour l’entrée donnée, je veux cette sortie:
/proc
/run
/tmp
/home/bytecommander/ramdisk
Comment cela peut-il être fait facilement dans la ligne de commande en utilisant par exemple Bash, sed
, awk
ou tout autre outil courant? Les solutions courtes qui correspondent à une seule ligne sont appréciées mais non requises.
$ 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
La façon dont cela fonctionne est assez simple, mais l'ordre des commandes est important. Nous commençons par enregistrer la première ligne et l’imprimer. Nous allons à la ligne suivante et vérifions si la ligne suivante contient le texte précédent. Si c'est le cas, nous ne faisons rien. Si ce n'est pas le cas, c'est un nouveau chemin différent.
L'approche initiale était défectueuse et a échoué lorsqu'il y avait des chemins adjacents avec la même sous-chaîne principale, tels que /var/zomg
et /var/zomgkthx
(Merci à Chai T.Rex pour l'avoir signalé). L'astuce consiste à ajouter "/" à l'ancien chemin pour signifier sa fin, rompant ainsi la sous-chaîne. La même approche est utilisée dans la variante python ci-dessous.
#!/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,'')
Échantillon échantillon:
$ ./reduce_paths.py paths.txt
/proc
/run
/tmp
/home/bytecommander/ramdisk
/var/zomg
/var/zomgkthx
/zomg
/zomgkthx
Cette approche est similaire à awk-one. L'idée est la même: enregistrez la première ligne et continuez à imprimer et à réinitialiser la variable de suivi uniquement lorsque nous rencontrons une ligne qui n'a pas de variable de suivi comme sous-chaîne de départ.
Une fois, vous pouvez également utiliser la fonction 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,'')
Une autre version de Python, utilisant la nouvelle bibliothèque 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))