exécuter une command dans $ PATH correspondant à un caractère générique

Je voudrais find et exécuter une command dans le $PATH courant correspondant à ce libreoffice?.? générique libreoffice?.? (par exemple, libreoffice4.3 , libreoffice4.3 , etc.)

EDIT: si plusieurs correspondances sont trouvées, vous pouvez en choisir une au hasard.

Je préfère une solution compatible POSIX.

Définissez IFS sur : pour split la valeur de PATH sur les deux-points. Si votre find a l'action -quit et la primitive -maxdepth primaire (par exemple FreeBSD, OSX, GNU), vous savez que la command existera et que vous ne vous souciez pas du code de return de la command, vous pouvez utiliser cette seule ligne:

 pattern='libreoffice?.?' IFS=:; find $PATH -maxdepth 1 -type f -name "$pattern" -exec {} \; -quit; unset IFS 

Cela ne fournit pas un moyen facile de signaler si la command a été trouvée. En outre, pour être plus robuste, désactivez la fonction Globulation si la valeur de PATH contient des caractères generics. En outre, il est possible d'avoir un composant vide dans PATH pour signifier le directory courant (mais mon conseil est d'utiliser . place). Le code ci-dessous prend en charge toutes ces complications.

 pattern='libreoffice?.?' case $PATH in :*) directories=.$PATH;; *::*) directories=${PATH%%::*}:.:${PATH#*::};; *:) directories=$PATH.;; *) directories=$PATH;; esac set -f; IFS=: cmd= for d in $directories; do set +f for x in "$d"/$pattern; do if [ -x "$x" ] && ! [ -d "$x" ]; then cmd=$x break fi done if [ -n "$cmd" ]; then break; fi done set +f; unset IFS if [ -z "$cmd" ]; then echo 1>&2 "$pattern: not found in PATH" exit 127 else exec "$cmd" fi 

Si vous utilisez zsh (contrairement à sh, bash, ksh, …), il est beaucoup plus simple de créer une solution robuste.

 pattern='libreoffice?.?' matches=($^path/$~pattern(N.*[1])) if ((!#matches)); then $matches[1] else echo 1>&2 "$pattern: not found in PATH" exit 127 fi 

Avec zsh :

 $commands[(i)libreoffice?.?] 

Dans zsh , $commands est un tableau associatif spécial dont les keys sont des noms de commands et leur path.

i ci-dessus est un indicateur d'indice de tableau qui indique à zsh faire correspondre le model aux keys du tableau et de returnner la première key correspondante.

Les éléments du tableau associatif ne sont pas dans un ordre particulier, donc la première key correspondante ne sera pas nécessairement la première qui se produit dans $PATH . Si vous voulez le libreoffice avec le plus grand numéro de version, vous pourriez faire:

 ${${(nO)${commands[(I)libreoffice?.?]}}[1]} 

L'indicateur I indice est étendu à toutes les keys correspondantes. Nous utilisons les drapeaux d'extension de paramètre n (sorting numérique) et O (inverse) pour sortinger cette list du plus grand au plus petit numéro de version, puis [1] pour sélectionner le premier.

Voir également:

 whence -m 'libreoffice?.?' 

pour find les paths des commands correspondantes.

Vous pouvez utiliser la command "find" comme suit

 find ./ -name "libreoffice?.?" 

Voici une alternative plus simple:

 $(compgen -c libreoffice) 

Il suppose bash, et suppose qu'il n'y a qu'un seul libreoffice* installé.

Il émule ce que l'exécution de l'onglet bash ferait si vous tapez l' onglet libreoffice .

Si vous tentiez délibérément d'exclure libreoffice sans numéro de version et que vous vouliez gérer l'existence de plusieurs versions, essayez:

 run_libreoffice() { compgen -c libreoffice | while read -r exe; do case "$exe" in libreoffice?.?) "$exe" "$@" return ;; esac done } run_libreoffice "$@" 

La déclaration de case fait correspondre seulement libreoffice?.? , et nous bouclons les résultats, en exécutant seulement le premier.

POSIXly, lors de la search d'une command exécutable $PATH 'd, vous devez utiliser la command :

  • command -v
    • Écrire une string dans la sortie standard qui indique le nom de path ou la command qui sera utilisé par le shell dans l'environnement d'exécution du shell courant (voir Environnement d'exécution de shell , pour appeler nom_command , mais n'appelez pas nom_command .
    • … Sinon, aucune sortie ne doit être écrite et le statut de sortie doit refléter que le nom n'a pas été trouvé.

Si elle est appelée sans commutateur, la command invoquera nom_command , mais vous avez toujours besoin d'un path complètement résolu pour résoudre complètement un shell glob .

Donc, pour cela, vous devriez vérifier le glob par rapport à chacun des éléments séparés par deux-points dans $PATH dans l'ordre, comme spécifié :

  • PATH
    • Cette variable doit représenter la séquence des préfixes de path que certaines fonctions et utilitaires appliquent dans la search d'un file exécutable connu uniquement par un nom de file . Les préfixes doivent être séparés par deux points:. Lorsqu'un préfixe de longueur différente de zéro est appliqué à ce nom de file, une barre oblique doit être insérée entre le préfixe et le nom de file. Un préfixe de longueur nulle est une fonctionnalité héritée qui indique le directory de travail en cours. Il apparaît sous la forme de deux points-virgules adjacents :: , en tant que premier côlon précédant le rest de la list ou en tant que deux-points consécutifs après le rest de la list. Une application ssortingctement conforme doit utiliser un path d'access réel (tel que . ) Pour représenter le directory de travail en cours dans PATH . La list doit être recherchée du début à la fin, en appliquant le nom de file à chaque préfixe, jusqu'à ce qu'un file exécutable avec le nom spécifié et les permissions d'exécution appropriées soit trouvé. Si le nom de path recherché contient une barre oblique, la search à travers les préfixes de path ne doit pas être effectuée. Si le path commence par une barre oblique, le path spécifié est résolu (voir Résolution du path ) . Si PATH est désactivé ou défini sur null, la search de path est définie par l'implémentation .

Vous pouvez répartir $PATH dans un champ par $PATH sur $IFS , mais vous ne pouvez le faire que si vous set -f également set -f extension nom_file comme étant désactivée, au cas où un composant de $PATH contiendrait [?* car la génération de nom de file (read: globbing) se produit après la division de champ dans l'ordre d'expansion du shell.

De cette façon, le seul moyen vraiment robuste de faire ce POSIXly est d'abord d'étendre la list de champs sur $IFS tandis que la génération de nom de file est désactivée et d'save le résultat de tableau, puis de nouveau activer la génération de nom de file et désactiver le champ- splitting (afin que vous puissiez stocker et étendre votre model de glob non guidé dans une variable sans risque d'être affecté par $IFS premier) et opérer sur les résultats à tour de rôle.

Heureusement, ce n'est pas si difficile:

 g='libreoffice?.?' ( IFS=:; set-f #split on :; disable filename gen set +f -- $PATH #store split array, enable filename gen after IFS= #disable field splitting for p do for x in ${p:+"$p/"$g} do command -v "$x" done;done ) #done in subshell to avoid affecting current shell 

Les derniers bits ne sont pas commentés car ils sont mieux expliqués ici.

  • for p do – La première boucle for associe de façon itérative $p la valeur de chaque paramètre de position dans le tableau "$@" du shell , qui est set sur l'extension split-champ de $PATH divisé par : colons.

  • for x in ${p:+"$p/"$g} – La boucle inner for fixe itérativement $x à chacune des expansions de ${p:+"$p/"$g} c'est-à-dire qu'il ne réitère rien du tout à less que $p soit à la fois défini et non nul. Ceci est important car :: ou un leader : splitait en champs nuls, et de cette façon, les deux loops seront ssortingctement conforms à la spécification $PATH (comme indiqué ci-dessus) en ne reconnaissant pas les champs de longueur nulle .

  • Il est important de noter qu'à cause de la façon dont $PATH est divisé alors que la génération de nom de file est désactivée et que "$p/"$g est résolu alors qu'il est activé mais que le fractionnement de champ a été désactivé, aucun des éléments de $PATH ne peut les champs sont divisés sur : (quel que soit les autres caractères qu'ils peuvent contenir) et aucune valeur pour "$p/"$g étendue à elle-même ou à n'importe quel nom de file (quel que soit le caractère de $p ou autre les caractères de correspondance de motifs $g peuvent contenir) .

  • command – et la dernière command imprime uniquement pour normaliser les valeurs de $x qui sont des commands exécutables.

Voici tout ce qui est enveloppé dans une fonction shell, avec la possibilité supplémentaire de gérer plusieurs arguments $g :

 glob_path()( for g do IFS=:; set -f set +f -- ${PATH:-.}; IFS= for p do for x in ${p:+"$p/"$g} do command -v "$x" done; done; done ) 

Cela devrait vous procurer une list d'exécutables correspondant à chaque argument que vous avez imprimé, une par ligne commandée en premier par ordre d'arguments, puis par priorité $PATH et enfin par ordre de sorting de vos parameters régionaux. Il ne devrait pas échouer sur les nouvelles lignes ou les caractères spéciaux dans les paths ou les templates de globes que vous le remettez. Il pourrait être modifié (voir l'historique des modifications ici) pour remplir fidèlement le tableau "$@" du shell à un exécutable par paramètre de position.

Vous pouvez également le modifier pour exécuter la première correspondance de glob réussie et quitter la search de cet argument glob particulier et passer au suivant si la command se termine avec succès comme …

 glob_path()( for g do IFS=:; set -f set +f -- ${PATH:-.}; IFS= for p do for x in ${p+:"$p/"$g} do command -v "$x" && command "$x" && break 2 done; done; done ) 

C'est un moyen POSIX portable de find votre match glob, mais certains shells n'ont même pas besoin de la seconde for g... loop. En bash , par exemple, cela fonctionnera aussi comme …

 glob_path()( for g do IFS=:; set -f set +f -- ${PATH:-.}; IFS= for p do command -v ${p:+"$p/"$g} done; done ) 

… qui répertorie toujours toutes les correspondances globes exécutables pour tous les arguments et pour chaque élément de $PATH que l'un de ces éléments ou glob contienne des lignes nouvelles ou des caractères spéciaux, etc.

Vous pouvez l'utiliser comme:

 glob_path '?sh' '??sh' #I'd rather not install libreoffice 

… qui pour moi imprime …

 /usr/bin/ksh /usr/bin/rsh /usr/bin/ssh /usr/bin/zsh /usr/local/bin/dash /usr/local/bin/yash /usr/bin/bash /usr/bin/bssh /usr/bin/chsh /usr/bin/dash /usr/bin/mksh /usr/bin/posh /usr/bin/slsh /usr/bin/yash 

Comme nous l' glob_path vu, la fonction glob_path interprète la variable d'environnement $PATH comme spécifiant qu'une application ssortingctement conforme doit ignorer les occurrences de glob_path , de fin ou consécutives dans la $PATH de $PATH , mais interprète un $PATH vide ou non $PATH pour signifier . .

Si vous vouliez cependant cette fonctionnalité héritée, la fonction pourrait être écrite:

 glob_path()( for g do IFS=:; set -f set +f -- ${0+$PATH:}; IFS= for p do for x in "${p:-.}/"$g do command -v "$x" done; done; done ) 

… qui interpréterait toutes les occurrences de l'interligne principal, du second interligne ou du deux-deux consécutives . (et ainsi searchr autant de fois qu'un composant $PATH longueur nulle apparaît dans la $PATH de $PATH ) , mais ce n'est pas ssortingctement conforme.