Os xargs usam alias em vez de binários

13

Bash 4.2 no CentOS 6.5:

No meu ~/.bash_profileeu tenho um monte de aliases, incluindo:

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

para que eu possa obter destaque de cores e imprimir números de linha automaticamente ao executar grep. Se eu executar o seguinte, o destaque funcionará conforme o esperado:

$ grep -Re 'regex_here' *.py

No entanto, quando eu executei isso recentemente:

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

os resultados não foram destacados e os números de linha não foram impressos, forçando-me a voltar e adicionar explicitamente -n --color=alwaysao grepcomando.

  • Não xargslê aliases no ambiente?
  • Se não, existe uma maneira de fazer isso?
MattDMo
fonte
Esta sessão de perguntas e respostas tem o que você deseja.
psimon
@psimon certo, isso significa dizer para fazer o que já fiz na minha solução alternativa - tive que expandir manualmente meu apelido no xargscomando. O que estou tentando descobrir é se existe uma maneira de chamar diretamente meu apelido xargs.
8894 MattelData de publicação
1
Você já tentou export GREP_OPTIONS='-n --color=always'antes do seu comando xargs?
doneal24
@ DougO'Neal obrigado, isso funcionou! Vou acrescentar isso ao meu .bash_profile. Sinta-se livre para escrever uma resposta ...
MattDMo

Respostas:

10

Um alias é interno ao shell em que está definido. Não é visível para outros processos. O mesmo vale para funções de shell. xargsé um aplicativo separado, que não é um shell, portanto não possui um conceito de alias ou funções.

Você pode fazer xargs chamar um shell em vez de chamar grepdiretamente. No entanto, apenas invocar um shell não é suficiente, você precisa definir o alias nesse shell também. Se o alias estiver definido no seu .bashrc, você pode obter esse arquivo; no entanto, isso pode não funcionar, você .bashrcrealiza outras tarefas que não fazem sentido em um shell não interativo.

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

Cuidado com os meandros da cotação aninhada ao digitar a regexp. Você pode simplificar sua vida passando o regexp como um parâmetro para o shell.

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

Você pode executar a pesquisa de alias explicitamente. Então xargsvai ver grep -n --color=always.

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

No zsh:

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

A propósito, observe que find … | xargs … quebras nos nomes de arquivos que contêm espaços (entre outros) . Você pode corrigir isso alterando para registros delimitados por nulo:

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

ou usando -exec:

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

Em vez de ligar find, você pode fazer tudo inteiramente dentro do shell. O padrão glob **/percorre diretórios recursivamente. No bash, você precisa executar shopt -s globstarpara ativar esse padrão glob primeiro.

grep regex_here **/*.py

Isso tem algumas limitações:

  • Se muitos arquivos corresponderem (ou se tiverem caminhos longos), o comando poderá falhar porque excede o comprimento máximo da linha de comando.
  • No bash ≤4.2 (mas não nas versões mais recentes, nem no ksh ou no zsh), ocorre novamente **/em links simbólicos para diretórios.

Outra abordagem é usar a substituição de processos, conforme sugerido por MariusMatutiae .

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

Isso é útil quando **/não é aplicável: para findexpressões complexas ou no bash ≤4.2 quando você não deseja se recuperar sob links simbólicos. Observe que isso quebra nos nomes de arquivos que contêm espaços; uma solução alternativa é definir IFSe desativar o globbing , mas está começando a ficar um pouco complexo:

(IFS=$'\n'; set -f; grep regex_here <(find . -name '*.py') )
Gilles 'SO- parar de ser mau'
fonte
obrigado pela explicação clara de por que aliases não são visíveis para outros processos
MattDMo
Pode-se também usar substituição de processo, veja minha resposta.
MariusMatutiae
11

Usar 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
fonte
Obrigado por isso, eu não sabia sobre o truque espacial à direita.
precisa saber é o seguinte
Np. Também é útil com sudo
1.61803 21/11/2015
2

Por favor, tome isso como uma demonstração de outra abordagem, que não consigo encontrar no pergunta SO :

Você pode escrever uma função de invólucro para xargs qual verifica se o primeiro argumento é um alias e, se houver, expanda-o de acordo.

Aqui está um código que faz exatamente isso, mas infelizmente requer o shell Z e, portanto, não roda 1: 1 com o bash (e, francamente, eu não estou acostumado com o bash o suficiente para portá-lo):

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

Prova de que funciona:

zsh%  alias grep = "grep -n" ´                           # inclui o número da linha de correspondência
zsh%  find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: 151: # data = teste
foo / bar.p1: 122: # data = teste # números de linha incluídos
zsh% unalias grep 
zsh% find foo -name "* .p *" | xargs grep -E test
foo / bar.p0: # data = teste
foo / bar.p1: # data = teste # números de linha não incluídos
zsh% 
mpy
fonte
1

Uma solução mais simples e elegante é usar a substituição de processo :

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

Ele não cria um novo shell como o pipe, o que significa que você ainda está no shell original onde o alias está definido e a saída é exatamente o que você deseja que seja.

Apenas tome cuidado para não deixar espaço entre o redirecionamento e os parênteses, caso contrário o bash gerará um erro. Que eu saiba, a substituição do processo é suportada por Bash, Zsh, Ksh {88,93}, mas não por pdksh (disseram-me que ainda não deve ser ).

MariusMatutiae
fonte
Eu acho que o desenvolvimento do pdksh está morto. Mksh é mais ou menos um projeto sucessor - “bem difícil, ao que parece (o conceito de análise é feito na cabeça de tg @)” .
Gilles 'SO- stop be evil'
A substituição de processo é um bom método para findcomandos complexos , embora você precise tomar cuidado para que ele se quebre em espaços e que não possa ser corrigido da maneira mais fácil find | xargspossível (alternando para -print0e -0ou usando -exec). Quando aplicável, **/é mais simples e mais robusto.
Gilles 'SO- stop be evil'
0

O grep lerá um conjunto de opções padrão da variável de ambiente GREP_OPTIONS. Se você vai colocar

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

no seu .bashrc, a variável será transferida para os subshells e você obterá os resultados esperados.

doneal24
fonte
Mas não colocar --line-numberou --color=alwaysem GREP_OPTIONSmenos que seja apenas para um comando, isso vai quebrar um monte de scripts. --color=autoé bom ter lá, e isso é tudo. Colocar essa linha no seu .bashrcvai quebrar muitas coisas.
Gilles 'SO- stop be evil'
@Gilles Definir aliases ou substituir opções padrão para qualquer comando da conta raiz é uma coisa ruim. É improvável que a configuração dessas opções para uma conta de usuário cause muitos problemas. Não consigo criar scripts de usuário problemáticos.
doneal24
Qualquer script que use grep de uma maneira que vá além do teste da presença de uma ocorrência será interrompido. Por exemplo, de /etc/init.d/cronno meu sistema: value=`egrep "^${var}=" "$ENV_FILE" | tail -n1 | cut -d= -f2` . Ou a partir de /usr/bin/pdfjam: pdftitl=`printf "%s" "$PDFinfo" | grep -e … | sed -e …` . Um alias não é um problema, pois não é visto em scripts.
Gilles 'SO- stop be evil'
@ Gilles Estou ciente de muitos scripts como este. As que eu não consigo entender são geralmente executadas apenas pelo root (como /etc/init.d/cron). Pessoalmente, não tenho nenhum alias definido em minhas contas de usuário, nem defino opções em um arquivo rc ou através de variáveis ​​de ambiente para substituir o comportamento padrão dos comandos. Prefiro previsibilidade do que conveniência.
doneal24
Os aliases não quebram a previsibilidade, pois não são vistos pelos scripts. Definir GREP_OPTIONSmuito a previsibilidade das quebras, exceto por algumas opções --color=auto( como foi para isso que foi projetado).
Gilles 'SO- stop be evil'