web-dev-qa-db-fra.com

Ayant une liste de chemins, comment puis-je filtrer les sous-répertoires des chemins mentionnés précédemment?

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.

7
Byte Commander

AWK

$ 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.

Alternative 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,'')

É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,'')
10
Sergiy Kolodyazhnyy

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))
8
muru