Haga que los xargs usen alias en lugar de binarios

13

Bash 4.2 en CentOS 6.5:

En mi ~/.bash_profiletengo un montón de alias, que incluyen:

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

para poder resaltar el color e imprimir números de línea automáticamente cuando se ejecuta grep. Si ejecuto lo siguiente, resaltar funciona como se esperaba:

$ grep -Re 'regex_here' *.py

Sin embargo, cuando ejecuté esto recientemente:

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

los resultados no se resaltaron y los números de línea no se imprimieron, lo que me obligó a retroceder y agregar explícitamente -n --color=alwaysal grepcomando.

  • ¿ xargsNo lee alias en el entorno?
  • Si no, ¿hay alguna manera de hacerlo?
MattDMo
fuente
Este Q&A tiene lo que quieres.
psimon
@psimon correcto, eso básicamente dice que haga lo que ya hice en mi solución: tuve que expandir manualmente mi alias en el xargscomando. Lo que estoy tratando de averiguar es si hay una forma desde la que pueda llamar directamente a mi alias xargs.
MattDMo
1
¿Has probado export GREP_OPTIONS='-n --color=always'antes tu comando xargs?
doneal24
@ DougO'Neal gracias, eso funcionó! Agregaré eso a mi .bash_profile. Siéntase libre de escribir una respuesta ...
MattDMo

Respuestas:

10

Un alias es interno al shell donde se define. No es visible para otros procesos. Lo mismo ocurre con las funciones de shell. xargses una aplicación separada, que no es un shell, por lo que no tiene un concepto de alias o funciones.

Puede hacer que xargs invoque un shell en lugar de invocarlo grepdirectamente. Sin embargo, invocar un shell no es suficiente, también debe definir el alias en ese shell. Si el alias está definido en su .bashrc, puede obtener ese archivo; sin embargo, esto puede no funcionar, .bashrcrealiza otras tareas que no tienen sentido en un shell no interactivo.

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

Tenga cuidado con las complejidades de las citas anidadas al escribir la expresión regular. Puede simplificar su vida pasando la expresión regular como parámetro al shell.

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

Puede realizar la búsqueda de alias explícitamente. Entonces xargsya veremos grep -n --color=always.

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

En zsh:

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

Por cierto, tenga en cuenta que se find … | xargs … rompe en los nombres de archivo que contienen espacios (entre otros) . Puede solucionar esto cambiando a registros delimitados por nulos:

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

o usando -exec:

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

En lugar de llamar find, puede hacer todo por completo dentro del shell. El patrón global **/atraviesa directorios de forma recursiva. En bash, shopt -s globstarprimero debe ejecutar para habilitar este patrón global.

grep regex_here **/*.py

Esto tiene algunas limitaciones:

  • Si coinciden muchos archivos (o si tienen rutas largas), el comando puede fallar porque excede la longitud máxima de la línea de comando.
  • En bash ≤4.2 (pero no en versiones más recientes, ni en ksh o zsh), se **/repite en enlaces simbólicos a directorios.

Otro enfoque es utilizar la sustitución de procesos, como lo sugiere MariusMatutiae .

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

Esto es útil cuando **/no es aplicable: para findexpresiones complejas , o en bash ≤4.2 cuando no desea recurrir bajo enlaces simbólicos. Tenga en cuenta que esto se rompe en los nombres de archivos que contienen espacios; Una solución alternativa es establecer IFSy deshabilitar el globbing , pero está empezando a ser un poco complejo:

(IFS=$'\n'; set -f; grep regex_here <(find . -name '*.py') )
Gilles 'SO- deja de ser malvado'
fuente
gracias por la explicación clara de por qué los alias no son visibles para otros procesos
MattDMo
También se puede usar la sustitución de procesos, vea mi respuesta.
MariusMatutiae
11

Utilizar 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
fuente
Gracias por eso, no sabía sobre el truco del espacio final.
MattDMo
Notario público. También es útil con sudo...
1.61803
2

Tome esto como una demostración de otro enfoque, que no puedo encontrar en la pregunta SO relacionada :

Puede escribir una función de contenedor para la xargsque comprueba si el primer argumento es un alias y, de ser así, expandirlo en consecuencia.

Aquí hay un código que hace exactamente eso, pero desafortunadamente requiere el shell Z y, por lo tanto, no se ejecuta 1: 1 con bash (y, francamente, no estoy acostumbrado a bash lo suficiente como para portarlo):

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]}"
}

Prueba de que funciona:

zsh% alias grep = "grep -n" ´                           # incluye el número de línea de coincidencia
zsh% find nombre-foo "* .p *" | xargs grep -E test
foo / bar.p0: 151: # datos = prueba
foo / bar.p1: 122: # datos = prueba # números de línea incluidos
zsh% unalias grep 
zsh% find nombre-foo "* .p *" | xargs grep -E test
foo / bar.p0: # datos = prueba
foo / bar.p1: # datos = prueba # números de línea no incluidos
zsh% 
mpy
fuente
1

Una solución más simple y elegante es utilizar la sustitución de procesos :

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

No crea un nuevo shell como lo hace la tubería, lo que significa que todavía está en su shell original donde se define el alias, y la salida es exactamente lo que desea que sea.

Solo tenga cuidado de no dejar espacio entre la redirección y el paréntesis, de lo contrario bash arrojará un error. A lo mejor de mi conocimiento, la sustitución del proceso es apoyada por Bash, Zsh, Ksh {88,93}, pero no por pdksh (me han dicho que debería ser un todavía no ).

MariusMatutiae
fuente
Creo que el desarrollo de pdksh está muerto. Mksh es más o menos un proyecto sucesor: "resulta bastante difícil (el concepto de análisis se realiza en la cabeza de tg @)" .
Gilles 'SO- deja de ser malvado'
Sustitución de proceso es un método bueno para complejos findcomandos, aunque es necesario tener cuidado con que se va a romper en espacios, y que no se puede arreglar tan fácilmente como find | xargspuede ser (por el cambio a -print0y -0, o el uso -exec). Cuando corresponde, **/es más simple y más robusto.
Gilles 'SO- deja de ser malvado'
0

grep leerá un conjunto de opciones predeterminadas de la variable de entorno GREP_OPTIONS. Si vas a poner

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

en su .bashrc, la variable se pasará a subcapas y obtendrá los resultados que espera.

doneal24
fuente
Pero no coloque --line-numberni ingrese --color=alwaysa GREP_OPTIONSmenos que sea solo para un comando, esto romperá muchos scripts. --color=autoestá bien tener allí, y eso es todo. Poner esta línea en tu .bashrcromperá muchas cosas.
Gilles 'SO- deja de ser malvado'
@Gilles Configurar cualquier alias u anular las opciones predeterminadas para cualquier comando para la cuenta raíz es algo malo. Es poco probable que configurar estas opciones para una cuenta de usuario cause muchos problemas. No puedo encontrar ningún script de usuario que sea problemático.
doneal24
Casi cualquier script que use grep de una manera que vaya más allá de probar la presencia de un evento se romperá. Por ejemplo, a partir /etc/init.d/cronde mi sistema: value=`egrep "^${var}=" "$ENV_FILE" | tail -n1 | cut -d= -f2` . Oa partir de /usr/bin/pdfjam: pdftitl=`printf "%s" "$PDFinfo" | grep -e … | sed -e …` . Un alias no es un problema ya que no se ve en los scripts.
Gilles 'SO- deja de ser malvado'
@Gilles Soy consciente de muchos guiones como este. Los que no puedo evitar generalmente se ejecutan solo por root (como /etc/init.d/cron). Personalmente, no tengo ningún alias definido en mis cuentas de usuario ni configuro opciones en un archivo rc o mediante variables de entorno para anular el comportamiento predeterminado de los comandos. Prefiero la previsibilidad sobre la conveniencia.
doneal24
Los alias no rompen la previsibilidad ya que no son vistos por los scripts. La configuración GREP_OPTIONSrompe la previsibilidad muy mal, excepto por algunas opciones como --color=auto(para lo que fue diseñada).
Gilles 'SO- deja de ser malvado'