Escopo de alias nas funções do bash

9

Eu uso um script (ao qual não tenho acesso de gravação) que cria vários aliases para configurar um ambiente. Gostaria de criar uma função bash para configurar meu ambiente, mas parece que os aliases não sobrevivem ao corpo da função.

Aqui está um exemplo mínimo:

# aliases.sh
alias fooAlias='echo "this will never work!"'  

.

# .bashrc
function setupLotsOfThings() {
    source aliases.sh
    fooAlias
}

.

Agora, se eu simplesmente fonte aliases.shinterativa, as coisas funcionam como esperado:

[mycomputer]~/ $ source aliases.sh
[mycomputer]~/ $ fooAlias
this will never work!

No entanto, se eu chamar a função definida no meu .bashrc, ela não reconhecerá o alias depois de obter sua definição:

[mycomputer]~/ $ setupLotsOfThings
-bash: fooAlias: command not found

O que está acontecendo aqui? Há algo que está faltando no escopo do aliascomando quando usado em uma função?

Edit: Vou adicionar alguns detalhes além do exemplo mínimo para esclarecer o que estou tentando realizar.

Para o meu trabalho, desenvolvo e executo muitos softwares em um cluster e / ou grade. Tenho vários projetos que exigem ambientes completamente diferentes, como versões diferentes do gcc, versões específicas de software, PATHs de configuração e dados e várias variáveis ​​de ambiente. Os administradores fornecem os scripts para configurar várias coisas, geralmente definindo funções ou aliases do shell, que invocam outras funções ou aliases ou executam vários scripts. Para mim, é uma caixa preta.

Eu gostaria de configurar meus próprios ambientes com um único comando. Atualmente, faço algo como:

[mycomputer]~/ $ source /some/environment/setup/script.sh
[mycomputer]~/ $ aliasToSetupSomeSoftwareVersion    #this was defined in the above
[mycomputer]~/ $ anotherAliasForOtherSoftware
[mycomputer]~/ $ source /maybe/theres/another/script.sh
[mycomputer]~/ $ runSomeOtherSetup      # this was defined in the new script

Esses comandos geralmente precisam ser executados em ordem. Minha idéia era basicamente copiar as linhas acima em um bloco de funções, mas, como mostra o exemplo original, isso simplesmente não funciona. Soluções alternativas são mais que bem-vindas!

correr atrás
fonte

Respostas:

10

Uma solução alternativa é colar esses comandos em um arquivo de texto em vez de em um bloco de funções. Algo como:

## This is needed to make the sourced aliases available
## within the script.
shopt -s expand_aliases

source /some/environment/setup/script.sh
aliasToSetupSomeSoftwareVersion
anotherAliasForOtherSoftware
source /maybe/theres/another/script.sh
runSomeOtherSetup

Salve isso como setup1.shonde quiser. O truque é originar este arquivo, não executá-lo:

$ source setup1.sh

Isso executará os aliases que estão no script e também os disponibilizará para o shell atual.

Você pode simplificar ainda mais o processo adicionando isso ao seu .bashrc:

alias setupLotsOfThings="source setup1.sh"

Agora você pode simplesmente executar setupLotsOfThingse obter o comportamento desejado da função.


Explicação

Há duas questões aqui. Primeiro, os aliases não estão disponíveis para a função em que estão declarados, mas apenas depois que a função é encerrada e, segundo, os aliases não estão disponíveis nos scripts. Ambos são explicados na mesma seção de man bash:

Os aliases não são expandidos quando o shell não é interativo, a menos que a opção do shell expand_aliases seja configurada usando shopt (consulte a descrição do shopt em SHELL BUILTIN COMMANDS abaixo).

As regras relativas à definição e uso de aliases são um tanto confusas. O Bash sempre lê pelo menos uma linha completa de entrada antes de executar qualquer um dos comandos nessa linha. Os aliases são expandidos quando um comando é lido, não quando é executado. Portanto, uma definição de alias que aparece na mesma linha que outro comando não entra em vigor até que a próxima linha de entrada seja lida. Os comandos que seguem a definição de alias nessa linha não são afetados pelo novo alias. Esse comportamento também é um problema quando as funções são executadas. Os aliases são expandidos quando uma definição de função é lida, não quando a função é executada, porque uma definição de função é ela mesma um comando composto. Como conseqüência, os aliases definidos em uma função não estarão
disponíveis até depois que a função for executada.
Para ser seguro, sempre coloque definições de alias em uma linha separada e não use alias em comandos compostos.

Depois, há a diferença entre executar e procurar um arquivo. Basicamente, a execução de um script faz com que ele seja executado em um shell separado, enquanto a fonte o faz com o shell atual. Portanto, a terceirização setup.shdisponibiliza os aliases para o shell pai, enquanto o executava como um script não.

Terdon
fonte
Bem, estou trabalhando em um cluster e tenho vários scripts de "alias" disponíveis que ajudam a configurar ambientes de software etc. O script de alias define muitos aliases diferentes; meu objetivo é invocar um ambiente específico, fornecendo os aliases corretos e executando (alguns) aliases. Prefiro fazer isso em um único comando, do que executar vários comandos em sequência.
chase
E PS, o script que configura os aliases é infelizmente muito detalhado e um pouco lento (como toca muitos arquivos no NFS), então eu prefiro não procurar todas essas coisas, por exemplo, no momento do login.
chase
@chase, mas eles só são fornecidos quando você executa setupLotsOfThings, eles simplesmente não estão disponíveis para a função em si. Eles funcionam no shell do qual você chamou a função. De qualquer forma, se sua função apenas originar aliases, por que não usar apenas um alias? Por exemplo: alias setupstuff="source aliases.sh".
terdon
certo, mas não estou preocupado com o escopo em si . Idealmente, eu só quero combinar o "material de origem" e os "aliases de execução" em um. Talvez isso possa ser feito com 3 funções? função sourceStuff () {source ...}; função runStuff () {someAlias; ...}; função setupLotsOfThings () {sourceStuff; runStuff; };
chase
@ Compre sim, pensei nisso, mas não funcionou :). Você poderia expandir sua pergunta com um pouco mais do que precisa fazer? As funções podem lidar apenas com tanta complexidade; talvez seja necessário escrever um pequeno script.
terdon
7

Na verdade, seus apelidos estão disponíveis após o carregamento da função! Você pode usá-los em seu shell interativo ou no seu .bashrcapós executar a função.

A restrição é que os aliases em uma definição de função são expandidos quando a definição da função é lida, não quando a função é avaliada. Essa é uma limitação do bash. Então, isso vai funcionar:

function setupLotsOfThings() {
    source aliases.sh
}
setupLotsOfThings
fooAlias

Mas não é isso:

function setupLotsOfThings() {
    source aliases.sh
}
function useTheAliases() {
    fooAlias
}
setupLotsOfThings
useTheAliases

Se você precisar de aliases que possam ser utilizados dentro de funções e que possam ser definidos após a análise da função, faça-os funcionar. Lembre-se de que você pode usar o commandbuiltin para chamar um comando externo a partir de uma função com o mesmo nome.

Gilles 'SO- parar de ser mau'
fonte