Décomposition des spécifications de path en préfixe le plus long-commun + suffixe

Étant donné que deux spécifications de path d'Unix absolues 1 , on pourrait décomposer chaque spécification comme la concaténation d'un plus long préfixe commun et d'un suffixe spécifique. Par exemple,

/abc/bcd/cdf -> /abc/bcd + cdf /abc/bcd/chi/hij -> /abc/bcd + chi/hij 

Existe-t-il un utilitaire Unix (ou utilitaires) pour calculer une telle décomposition? (J'ai ajouté «ou utilitaires» dans le cas où il existe des utilitaires distincts pour calculer le préfixe commun le plus long et pour calculer les paths relatifs).

(Je me rends count qu'il ne serait pas extrêmement difficile de coder de tels utilitaires, mais j'essaie de donner la priorité aux outils plus ou less standard par rapport aux outils personnalisés, dans la mesure du possible.)

1 J'écris "path spec" plutôt que "path" pour contourner des problèmes comme l'existence (des paths) dans un système de files donné, des liens, etc.

Vous pouvez le faire dans une boucle shell. Le code ci-dessous devrait fonctionner avec toutes sortes de paths étranges avec des barres obliques supplémentaires; si tous vos paths sont de la forme /foo/bar , vous pouvez vous en sortir avec quelque chose de plus simple.

 split_common_prefix () { path1=$1 path2=$2 common_prefix= ## Handle initial // specially case $path1 in //[!/]*) case $path2 in //[!/]*) common_prefix=/ path1=${path1#/} path2=${path2#/};; *) return;; esac;; /*) case $path2 in /*) :;; *) return;; esac;; *) case $path2 in /*) return;; esac;; esac ## Normalize multiple slashes trailing_slash1= trailing_slash2= case $path1 in */) trailing_slash1=/;; esac case $path2 in */) trailing_slash2=/;; esac path1=$(printf %s/ "$path1" | tr -s / /) path2=$(printf %s/ "$path2" | tr -s / /) if [ -z "$trailing_slash1" ]; then path1=${path1%/}; fi if [ -z "$trailing_slash2" ]; then path2=${path2%/}; fi ## Handle the complete prefix case (faster, necessary for equality and ## for some cases with trailing slashes) case $path1 in "$path2") common_prefix=$path1; path1= path2= return;; "$path2"/*) common_prefix=$path2; path1=${path1#$common_prefix} path2= return;; esac case $path2 in "$path1"/*) common_prefix=$path1; path1= path2=${path2#$common_prefix} return;; esac ## Handle the generic case while prefix1=${path1%%/*} prefix2=${path2%%/*} [ "$prefix1" = "$prefix2" ] do common_prefix=$common_prefix$prefix1/ path1=${path1#$prefix1/} path2=${path2#$prefix1/} done } 

Vous pouvez également déterminer le préfixe commun le plus long des deux strings et le couper à son dernier caractère (sauf lorsque le préfixe commun est constitué uniquement de barres obliques).

Vous pouvez calculer la plus longue sous-string principale commune d'une list de lignes avec ceci:

 sed -e '1{h;d;}' -e 'G;s,\(.*\).*\n\1.*,\1,;h;$!d' 

Qui par exemple pour:

 /abc/bcd/cdf /abc/bcd/cdf/foo /abc/bcd/chi/hij /abc/bcd/cdd 

résultats:

 /abc/bcd/c 

Pour le restreindre aux composants de path:

 sed -e 's,$,/,;1{h;d;}' -e 'G;s,\(.*/\).*\n\1.*,\1,;h;$!d;s,/$,,' 

(renvoie /abc/bcd sur l'échantillon ci-dessus).

Il n'y a pas un tel outil à ma connaissance. Cependant, vous pouvez facilement écrire un tel programme puisque vous devez déterminer le plus long groupe de composants.

Un exemple "one-liner":

echo /abc/bcd/cdf | awk -vpath=/abc/bcd/chi/hij -F/ '{ OFS="\n";len=0; split(path, components); for (i=1; i<=NF; i++) if($i == components[i])len+=1+length($i);else break;print substr($0, 1, len - 1), substr($0, len + 1), substr(path, len + 1);exit;}

Version formatée avec commentaires:

 $ cat longest-path.awk #!/usr/bin/awk -f BEGIN { FS="/"; # split by slash } { len=0; # initially the longest path has length 1 split(path, components); # split by directory separator (slash) for (i=1; i<=NF; i++) { # loop through all path components if ($i == components[i]) { len += 1 + length($i); } else { break; # if there is a mismatch, terminate } } print substr($0, 1, len - 1); # longest prefix minus slash print substr($0, len + 1); # remainder stdin print substr(path, len + 1); # remainder path exit; # only the first line is compared } $ echo /abc/bcd/cdf | ./longest-path.awk -vpath=/abc/bcd/chi/hij /abc/bcd cdf chi/hij 

Voici un quickie qui semble répondre à la question, faisant bon usage des fonctionnalités habituelles (standard?) D'unix / linux comme demandé (enfin … je l'ai essayé seulement sur mon Mageia Linux).

 #!/bin/sh # Compute absolute pathnames common prefix and decompose second one # Author Babou 2013/05/27 on http://unix.stackexchange.com/questions/67078/ first=`realpath -ms "$1"` rel=`realpath -ms --relative-to="$1" "$2" | rev` while [ `basename "$rel"` == '..' ] do first=`dirname "$first"` rel=`dirname "$rel"` done echo $first + `echo $rel | rev` 

Et ma suite de tests:

 ./prefix /abc/bcd/cdf /abc/bcd/chi/hij ./prefix "/abc/bcd/cdf" "/abc/bcd/chi/hij" ./prefix "/ab c/bcd/cdf" "/ab c/bcd/chi/hij" ./prefix "/abc/bcd/cdf" "/abc/bcd/chi/h ij" ./prefix "/" "/" ./prefix "/abc/bcd/" "/abc/bcd/chi/hij" ./prefix "/abc/bcd/cdf" "/abc/bcd/" ./prefix "/abc///zzz/../bcd/cdf" "///abc/bcd//chi/hij/" ./prefix "/abèc/bcd/cdf" "/abèc/bcd/" 

deux exemples:

 $ ./prefix "/abc///zzz/../bcd/cdf" "///abc/bcd//chi/hij/" /abc/bcd + chi/hij $ ./prefix "/abèc/bcd/cdf" "/abèc/bcd/" /abèc/bcd + . 

Si vous souhaitez décomposer les deux paths, vous pouvez modifier le script ou l'appliquer deux fois, en changeant l'ordre ou les arguments.

Je ne suis pas très heureux avec les noms de variables … mais ma première mauvaise note dans la programmation était due à une conversion d'alpha échouée (une occurrence oubliée). Donc, je le laisse tel quel.

PS Vous pouvez vouloir unifier la présentation du path relatif (deuxième partie de la décomposition) lorsqu'il est vide: il peut venir comme "." ou "/" dans un cas, lorsque les deux paths sont juste "/".