Tem xargs usar alias em vez de binário

11

Bash 4.2 no CentOS 6.5:

No meu ~/.bash_profile , tenho vários aliases, incluindo:

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

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

$ grep -Re 'regex_here' *.py

No entanto, quando eu corri 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=always ao comando grep .

  • O xargs não lê aliases no ambiente?
  • Se não, existe uma maneira de fazer isso?
por MattDMo 08.07.2014 / 18:37

5 respostas

8

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

Você pode fazer com que o xargs invoque um shell em vez de invocar grep diretamente. No entanto, apenas invocar um shell não é suficiente, você tem que definir o alias nesse shell também. Se o alias estiver definido em seu .bashrc , você poderá fornecer esse arquivo; no entanto, isso pode não funcionar, pois o .bashrc realiza 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 citação aninhada ao digitar o 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, xargs 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 … quebra em nomes de arquivos contendo 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 chamar find , você pode fazer tudo completamente dentro do shell. O padrão glob **/ atravessa os diretórios recursivamente. No bash, você precisa executar shopt -s globstar para ativar este 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 comandos.
  • No bash ≤4.2 (mas não nas versões mais recentes, nem no ksh ou zsh), **/ recorre a links simbólicos para diretórios.

Outra abordagem é usar a substituição do processo, conforme sugerido pela MariusMatutiae .

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

Isso é útil quando **/ não é aplicável: para expressões find complexas ou na bash ≤4.2 quando você não deseja recorrer a links simbólicos. Observe que isso quebra em nomes de arquivos contendo espaços; uma solução alternativa é set IFS e desabilitar o globbing , mas está começando a ficar um pouco complexo:

(IFS=$'\n'; set -f; grep regex_here <(find . -name '*.py') )
    
por 09.07.2014 / 02:32
9

Use 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.
    
por 21.11.2015 / 02:39
2

Por favor, tome isso como uma demonstração de outra abordagem, que não encontro na pergunta SO : p>

Você pode escrever uma função de wrapper para xargs , que verifica se o primeiro argumento é um alias e, em caso afirmativo, expanda-o de acordo.

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

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"´                          # include line number of match
zsh% find foo -name "*.p*" | xargs grep -E test
foo/bar.p0:151:#data=test
foo/bar.p1:122:#data=test                           # line numbers included
zsh% unalias grep
zsh% find foo -name "*.p*" | xargs grep -E test
foo/bar.p0:#data=test
foo/bar.p1:#data=test                               # line numbers not included
zsh% 
    
por 08.07.2014 / 19:20
1

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

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 lançará um erro. Tanto quanto sei, a substituição de processos é suportada por Bash, Zsh, Ksh {88,93}, mas não por pdksh (me disseram que deveria ser um ainda não ).

    
por 09.07.2014 / 17:30
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 então a variável será passada para as subshells e você obterá os resultados esperados.

    
por 09.07.2014 / 19:24