Demander à xargs d'utiliser un alias au lieu du binaire

13

Bash 4.2 sur CentOS 6.5:

Dans mon, ~/.bash_profilej'ai un tas d'alias, y compris:

alias grep='grep -n --color=always'

afin que je puisse obtenir la surbrillance des couleurs et imprimer automatiquement les numéros de ligne lors de l'exécution grep. Si je lance ce qui suit, la mise en surbrillance fonctionne comme prévu:

$ grep -Re 'regex_here' *.py

Cependant, quand je l'ai exécuté récemment:

$ find . -name '*.py' | xargs grep -E 'regex_here'

les résultats n'étaient pas mis en surbrillance et les numéros de ligne n'étaient pas imprimés, me forçant à revenir en arrière et à ajouter explicitement -n --color=alwaysà la grepcommande.

  • Ne xargslit pas les alias dans l'environnement?
  • Sinon, existe-t-il un moyen de le faire?
MattDMo
la source
Ce Q&A a ce que vous voulez.
psimon
@psimon à droite, c'est essentiellement dire de faire ce que j'ai déjà fait dans ma solution: j'ai dû développer manuellement mon alias dans la xargscommande. Ce que j'essaie de savoir, c'est s'il y a un moyen d'appeler directement mon alias xargs.
MattDMo
1
Avez-vous essayé export GREP_OPTIONS='-n --color=always'avant votre commande xargs?
doneal24
@ DougO'Neal merci, ça a marché! Je vais ajouter cela à mon .bash_profile. N'hésitez pas à rédiger une réponse ...
MattDMo

Réponses:

10

Un alias est interne au shell où il est défini. Il n'est pas visible pour les autres processus. Il en va de même pour les fonctions shell. xargsest une application distincte, qui n'est pas un shell, donc n'a pas de concept d'alias ou de fonctions.

Vous pouvez faire en sorte que xargs invoque un shell au lieu d'invoquer grepdirectement. Cependant, l'invocation d'un shell ne suffit pas, vous devez également définir l'alias dans ce shell. Si l'alias est défini dans votre .bashrc, vous pouvez source ce fichier; cependant, cela peut ne pas fonctionner, vous .bashrceffectuez d'autres tâches qui n'ont pas de sens dans un shell non interactif.

find . -name '*.py' | xargs bash -c '. ~/.bashrc; grep -E regex_here "$@"' _

Méfiez-vous des subtilités des citations imbriquées lors de la frappe de l'expression rationnelle. Vous pouvez simplifier votre vie en passant l'expression rationnelle en tant que paramètre au shell.

find . -name '*.py' | xargs bash -c '. ~/.bashrc; grep -E "$0" "$@"' regex_here

Vous pouvez effectuer la recherche d'alias de manière explicite. Alors xargsverra grep -n --color=always.

find . -name '*.py' | xargs "${BASH_ALIASES[grep]}" regex_here

En zsh:

find . -name '*.py' | xargs $aliases[grep] regex_here

Soit dit en passant, il se find … | xargs … casse les noms de fichiers contenant des espaces (entre autres) . Vous pouvez résoudre ce problème en modifiant les enregistrements délimités par des valeurs nulles:

find . -name '*.py' -print0 | xargs -0 "${BASH_ALIASES[grep]}" regex_here

ou en utilisant -exec:

find . -name '*.py' -exec "${BASH_ALIASES[grep]}" regex_here {} +

Au lieu d'appeler find, vous pouvez tout faire entièrement à l'intérieur du shell. Le modèle glob **/parcourt les répertoires de manière récursive. Dans bash, vous devez d'abord exécuter shopt -s globstarce modèle global.

grep regex_here **/*.py

Cela a quelques limitations:

  • Si un grand nombre de fichiers correspondent (ou s'ils ont de longs chemins d'accès), la commande peut échouer car elle dépasse la longueur de ligne de commande maximale.
  • En bash ≤4,2 (mais pas dans les versions plus récentes, ni dans ksh ou zsh), se **/reproduit en liens symboliques vers des répertoires.

Une autre approche consiste à utiliser la substitution de processus, comme suggéré par MariusMatutiae .

grep regex_here <(find . -name '*.py')

Ceci est utile lorsque cela **/ne s'applique pas: pour les findexpressions complexes , ou en bash ≤4,2 lorsque vous ne voulez pas récuser sous des liens symboliques. Notez que cela rompt sur les noms de fichiers contenant des espaces; une solution consiste à définir IFSet désactiver la globalisation , mais cela commence à devenir un peu complexe:

(IFS=$'\n'; set -f; grep regex_here <(find . -name '*.py') )
Gilles 'SO- arrête d'être méchant'
la source
merci pour l'explication claire des raisons pour lesquelles les alias ne sont pas visibles par les autres processus
MattDMo
On peut également utiliser la substitution de processus, voir ma réponse.
MariusMatutiae
11

Utilisation alias xargs='xargs '

alias: alias [-p] [name[=value] ... ]
(snip)
A trailing space in VALUE causes the next word to be checked for
alias substitution when the alias is expanded.
1,61803
la source
Merci pour cela, je ne connaissais pas l'astuce de l'espace de fuite.
MattDMo
Np. C'est aussi utile avec sudo
1.61803
2

Veuillez prendre cela comme une démonstration d'une autre approche, que je ne trouve pas dans la question SO correspondante :

Vous pouvez écrire une fonction wrapper pour xargslaquelle vérifie si le premier argument est un alias et si c'est le cas, développez-le en conséquence.

Voici un code qui fait exactement cela mais malheureusement, il nécessite le shell Z et ne s'exécute donc pas 1: 1 avec bash (et franchement, je n'ai pas l'habitude de bash assez pour le porter):

xargs () {
        local expandalias
        if [[ $(which $1) =~ "alias" ]]; then
                expandalias=$(builtin alias $1) 
                expandalias="${${(s.'.)expandalias}[2]}"
        else
                expandalias=$1
        fi
        command xargs ${(z)expandalias} "${(z)@[2,-1]}"
}

Preuve que ça marche:

zsh% alias grep = "grep -n" ´                           # inclut le numéro de ligne de correspondance
zsh% find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: 151: # data = test
foo / bar.p1: 122: # data = test # numéros de ligne inclus
zsh% unalias grep 
zsh% find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: # data = test
foo / bar.p1: # data = test # numéros de ligne non inclus
zsh% 
mpy
la source
1

Une solution plus simple et plus élégante consiste à utiliser la substitution de processus :

grep -E 'regex_here' <( find . -name '*.py')

Il ne crée pas un nouveau shell comme le fait le tuyau, ce qui signifie que vous êtes toujours dans votre shell d'origine où l'alias est défini, et la sortie est exactement ce que vous souhaitez qu'elle soit.

Faites juste attention à ne laisser aucun espace entre la redirection et les parenthèses, sinon bash générera une erreur. À ma connaissance, la substitution de processus est prise en charge par Bash, Zsh, Ksh {88,93}, mais pas par pdksh (on me dit que cela ne devrait pas encore l'être ).

MariusMatutiae
la source
Je pense que le développement de pdksh est mort. Mksh est plus ou moins un projet successeur - «assez dur, il s'avère (le concept d'analyse se fait dans la tête de tg @)» .
Gilles 'SO- arrête d'être méchant'
La substitution de processus est une bonne méthode pour les findcommandes complexes , bien que vous deviez prendre garde qu'elle ne se brisera pas sur les espaces, et cela ne peut pas être corrigé aussi facilement que find | xargspossible (en basculant vers -print0et -0, ou en utilisant -exec). Le cas échéant, **/est plus simple et plus robuste.
Gilles 'SO- arrête d'être méchant'
0

grep lira un ensemble d'options par défaut à partir de la variable d'environnement GREP_OPTIONS. Si vous voulez mettre

 export GREP_OPTIONS='--line-number --color=always'

dans votre .bashrc, la variable sera transmise aux sous-coquilles et vous obtiendrez les résultats attendus.

doneal24
la source
Mais ne mettez pas dedans --line-numberou --color=alwaysà GREP_OPTIONSmoins que ce soit pour une seule commande, cela cassera beaucoup de scripts. --color=autoc'est bien d'y avoir, et c'est à peu près tout. Mettre cette ligne dans votre .bashrcvolonté cassera beaucoup de choses.
Gilles 'SO- arrête d'être méchant'
@Gilles Définir des alias ou remplacer les options par défaut pour toute commande pour le compte root est une mauvaise chose. La définition de ces options pour un compte d'utilisateur est peu susceptible de provoquer de nombreux problèmes. Je ne peux pas proposer de scripts utilisateur problématiques.
doneal24
À peu près n'importe quel script qui utilise grep d'une manière qui va au-delà du test de la présence d'une occurrence se cassera. Par exemple, à partir /etc/init.d/cronde mon système: value=`egrep "^${var}=" "$ENV_FILE" | tail -n1 | cut -d= -f2` . Ou de /usr/bin/pdfjam: pdftitl=`printf "%s" "$PDFinfo" | grep -e … | sed -e …` . Un alias n'est pas un problème car il n'est pas vu dans les scripts.
Gilles 'SO- arrête d'être méchant'
@Gilles Je connais de nombreux scripts comme celui-ci. Ceux que je ne peux pas contourner sont généralement gérés uniquement par root (comme /etc/init.d/cron). Personnellement, je n'ai pas d' alias définis sur mes comptes d'utilisateur et je ne définis pas d'options dans un fichier rc ou via des variables d'environnement pour remplacer le comportement par défaut des commandes. Je préfère la prévisibilité à la commodité.
doneal24
Les alias ne cassent pas la prévisibilité car ils ne sont pas vus par les scripts. Le réglage GREP_OPTIONSrompt très mal la prévisibilité, à l'exception de quelques options comme --color=auto(pour lesquelles il a été conçu).
Gilles 'SO- arrête d'être méchant'