J'essaie d'écrire une fonction shell bash qui me permettra de supprimer les copies dupliquées des répertoires de ma variable d'environnement PATH.
On m'a dit qu'il est possible d'y parvenir avec une commande d'une ligne à l'aide de la awk
commande, mais je ne sais pas comment le faire. Quelqu'un sait comment?
Réponses:
Si vous n'avez pas déjà de doublons dans le
PATH
et que vous voulez seulement ajouter des répertoires s'ils ne sont pas déjà là, vous pouvez le faire facilement avec le shell seul.Et voici un extrait de shell qui supprime les doublons
$PATH
. Il passe en revue les entrées une par une et copie celles qui n'ont pas encore été vues.la source
PATH=$PATH:x=b
le x dans PATH d'origine peut avoir la valeur a; la valeur prendra effet.PATH=x:$PATH
.PATH=$PATH:...
nonPATH=...:$PATH
. Il est donc plus approprié d'itérer un ordre inversé. Même si votre méthode fonctionne également, alors les gens s’appliquent de la manière inverse.Voici une solution intelligible à une ligne qui fait tout ce qui est bien: élimine les doublons, préserve l’ordre des chemins et n’ajoute pas deux points à la fin. Cela devrait donc vous donner un PATH dédupliqué qui donne exactement le même comportement que l'original:
Il se divise simplement en deux-points (
split(/:/, $ENV{PATH})
), utilise desgrep { not $seen{$_}++ }
filtres pour filtrer toutes les occurrences répétées des chemins, à l'exception de la première, puis réunit les autres en les séparant par des points et affiche le résultat (print join(":", ...)
).Si vous voulez un peu plus de structure autour de cela, ainsi que la possibilité de dédupliquer d'autres variables, essayez cet extrait, que j'utilise actuellement dans ma propre configuration:
Ce code dédupliquera PATH et MANPATH, et vous pourrez facilement appeler
dedup_pathvar
d'autres variables contenant des listes de chemins séparées par des points (par exemple, PYTHONPATH).la source
chomp
pour supprimer une nouvelle ligne. Cela a fonctionné pour moi:perl -ne 'chomp; print join(":", grep { !$seen{$_}++ } split(/:/))' <<<"$PATH"
En voici une élégante:
Plus long (pour voir comment ça marche):
Ok, puisque vous êtes nouveau sur Linux, voici comment configurer PATH sans un ":"
Par ailleurs, assurez-vous de ne PAS avoir de répertoires contenant ":" dans votre PATH, sinon cela va être gâché.
un peu de crédit à:
la source
echo -n
. Vos commandes ne semblent pas fonctionner avec "ici les chaînes", par exemple, essayez:awk -v RS=: -v ORS=: '!arr[$0]++' <<< ".:/foo/bin:/bar/bin:/foo/bin"
Voici une doublure AWK.
où:
printf %s "$PATH"
imprime le contenu de$PATH
sans nouvelle ligneRS=:
modifie le caractère séparateur de l'enregistrement d'entrée (par défaut, nouvelle ligne)ORS=
change le délimiteur d'enregistrement de sortie en chaîne videa
le nom d'un tableau créé implicitement$0
référence l'enregistrement en coursa[$0]
est une déréférence de tableau associatif++
est l'opérateur post-incrément!a[$0]++
garde le côté droit, c’est-à-dire qu’il s’assure que l’enregistrement en cours est uniquement imprimé, s’il n’a pas été imprimé auparavantNR
le numéro d'enregistrement actuel, en commençant par 1Cela signifie que AWK est utilisé pour scinder le
PATH
contenu le long des:
caractères de délimitation et pour filtrer les entrées en double sans modifier l'ordre.Comme les tableaux associatifs AWK sont implémentés sous forme de tables de hachage, le temps d'exécution est linéaire (c'est-à-dire dans O (n)).
Notez que nous n’avons pas besoin de rechercher les
:
caractères entre guillemets , car les shells ne fournissent pas de guillemets pour prendre en charge les répertoires avec:
son nom dans laPATH
variable.Awk + coller
Ce qui précède peut être simplifié avec coller:
La
paste
commande est utilisée pour intercaler la sortie awk avec des deux points. Cela simplifie l’action awk lors de l’impression (action par défaut).Python
La même chose que Python deux-liner:
la source
paste
commande ne fonctionne pas pour moi sauf si j'ajoute une fin-
pour utiliser STDIN.-v
sinon je reçois une erreur.-v RS=: -v ORS=
. Juste différents types deawk
syntaxe.Une discussion similaire a eu lieu à ce sujet ici .
Je prends une approche légèrement différente. Au lieu d'accepter simplement le PATH défini parmi tous les différents fichiers d'initialisation installés, je préfère utiliser
getconf
pour identifier le chemin système et le placer en premier, puis ajouter mon ordre de chemin préféré, puis utiliserawk
pour supprimer les doublons. Cela peut ou ne peut pas vraiment accélérer l'exécution de la commande (et en théorie être plus sûr), mais cela me donne des fuzzies chaleureux.la source
:
à laPATH
(c.-à-d. Une entrée de chaîne vide), car alors le répertoire de travail en cours fait partie de votrePATH
.Tant que nous ajoutons des imprimeurs non-awk:
(Peut-être aussi simple que cela,
PATH=$(zsh -fc 'typeset -U path; echo $PATH')
mais zsh lit toujours au moins unzshenv
fichier de configuration qui peut être modifiéPATH
.)Il utilise deux fonctionnalités intéressantes de zsh:
typeset -T
)typeset -U
).la source
Cela utilise perl et présente plusieurs avantages:
/usr/bin:/sbin:/usr/bin
entraînera/usr/bin:/sbin
)la source
Aussi
sed
(ici, en utilisant lased
syntaxe GNU ) peut faire le travail:celui-ci ne fonctionne bien que si le premier chemin est
.
comme dans l'exemple de dogbane.En général, vous devez ajouter une autre
s
commande:Cela fonctionne même sur une telle construction:
la source
Comme d'autres l'ont démontré, il est possible d'utiliser awk, sed, perl, zsh ou bash sur une ligne, en fonction de votre tolérance aux longues lignes et de votre lisibilité. Voici une fonction bash
fonction bash
usage
Pour supprimer les doublons de PATH
la source
Ceci est ma version:
Usage:
path_no_dup "$PATH"
Exemple de sortie:
la source
Les versions récentes de bash (> = 4) également des tableaux associatifs, c’est-à-dire que vous pouvez également utiliser un bash 'one liner':
où:
IFS
change le séparateur de champ d'entrée en:
declare -A
déclare un tableau associatif${a[$i]+_}
est un paramètre expansion qui signifie:_
est substitué si et seulement sia[$i]
est défini. Ceci est similaire à celui${parameter:+word}
qui teste également pour non-null. Ainsi, dans l'évaluation suivante du conditionnel, l'expression_
(c'est-à-dire une chaîne de caractères unique) est évaluée à true (cela équivaut à-n _
), tandis qu'une expression vide est évaluée à false.la source
${a[$i]+_}
en modifiant votre réponse et en ajoutant une puce. Le reste est parfaitement compréhensible, mais vous m'y avez perdu. Je vous remercie.Explication du code awk:
En plus d'être laconique, ce one-liner est rapide: awk utilise une table de hachage pour obtenir une performance amortie de O (1).
basé sur Supprimer les entrées $ PATH en double
la source
if ( !x[$i]++ )
. Merci.Utilisez
awk
pour diviser le chemin:
, puis passez en boucle sur chaque champ et stockez-le dans un tableau. Si vous rencontrez un champ qui est déjà dans le tableau, cela signifie que vous l'avez déjà vu, alors ne l'imprimez pas.Voici un exemple:
(Mise à jour pour supprimer la fin
:
.)la source
Une solution - pas aussi élégante que celles qui modifient les variables * RS, mais peut-être raisonnablement claire:
L'ensemble du programme fonctionne dans les blocs BEGIN et END . Il extrait votre variable PATH de l’environnement et le divise en unités. Il itère ensuite sur le tableau résultant p (qui est créé dans l'ordre
split()
). Le tableau e est un tableau associatif utilisé pour déterminer si nous avons déjà vu ou non l'élément de chemin actuel (par exemple, / usr / local / bin ), et si ce n'est pas le cas, est ajouté à np , avec la logique pour ajouter un point-virgule à np s'il y a déjà du texte dans np . Le bloc END renvoie simplement np . Cela pourrait être encore simplifié en ajoutant le-F:
flag, en éliminant le troisième argument desplit()
(comme il est par défaut FS ), et en passantnp = np ":"
ànp = np FS
, nous donnant:Naïvement, je pensais que
for(element in array)
cela préserverait l'ordre, mais ce n'est pas le cas, donc ma solution initiale ne fonctionne pas, car les gens s'énerveraient si quelqu'un changeait soudainement l'ordre de leur$PATH
:la source
Seule la première occurrence est conservée et l'ordre relatif est bien maintenu.
la source
Je le ferais simplement avec des outils de base tels que tr, sort et uniq:
S'il n'y a rien de spécial ou d'étrange sur votre chemin, ça devrait marcher
la source
sort -u
au lieu desort | uniq
.